diff --git a/silver b/silver index 0b8ab5ce8..e7203458c 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 0b8ab5ce88c8cff4e6d9fb524657bbd857fb9850 +Subproject commit e7203458cb63f85730c286ff0151016d057f8c2d diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 33250b52d..95a0d6e73 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._ @@ -691,6 +692,78 @@ 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 enableDependencyAnalysis: ScallopOption[Boolean] = opt[Boolean]("enableDependencyAnalysis", + descr = "Enable dependency analysis mode", + default = Some(false), + noshort = true + ) + + val enableDependencyAnalysisDebugging: ScallopOption[Boolean] = opt[Boolean]("enableDependencyAnalysisDebugging", + descr = "Enable debugging for dependency 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), + noshort = true + ) + + 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 startDependencyAnalysisTool: ScallopOption[Boolean] = opt[Boolean]("startDependencyAnalysisTool", + descr = "Starts the dependency analysis command line tool after verification", + default = Some(false), + 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), + 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) { @@ -738,6 +811,66 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { validateFileOpt(multisetAxiomatizationFile) validateFileOpt(sequenceAxiomatizationFile) + validateOpt(enableDependencyAnalysis, parallelizeBranches) { + case (Some(false), _) => Right(()) + case (_, Some(false)) => Right(()) + case (Some(true), Some(true)) => + Left(s"Option ${enableDependencyAnalysis.name} is not supported in combination with ${parallelizeBranches.name}") + case other => + sys.error(s"Unexpected combination: $other") + } + + 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 ${enableDependencyAnalysis.name} requires ${rawProverArgs.name} with \"proof=true unsat-core=true\"") + } + + validateOpt(dependencyAnalysisExportPath, enableDependencyAnalysis) { + case (None, _) => Right(()) + case (Some(_), Some(true)) => Right(()) + case (Some(_), Some(false)) => + Left(s"Option ${dependencyAnalysisExportPath.name} requires option ${enableDependencyAnalysis.name}") + } + + validateOpt(executeDependencyAnalysisTests, enableDependencyAnalysis) { + case (Some(false), _) => Right(()) + case (_, Some(true)) => Right(()) + case (_, _) => + Left(s"Option ${executeDependencyAnalysisTests.name} requires option ${enableDependencyAnalysis.name}") + } + + validateOpt(startDependencyAnalysisTool, enableDependencyAnalysis) { + case (Some(false), _) => Right(()) + case (_, Some(true)) => Right(()) + case (_, _) => + 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(()) + 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/Silicon.scala b/src/main/scala/Silicon.scala index b9285840a..8ae57e73e 100644 --- a/src/main/scala/Silicon.scala +++ b/src/main/scala/Silicon.scala @@ -6,27 +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.frontend.{DefaultStates, MinimalViperFrontendAPI, SilFrontend, ViperFrontendAPI} import viper.silver.logger.ViperStdOutLogger -import viper.silver.utility.{FileProgramSubmitter} +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 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/debugger/SiliconDebugger.scala b/src/main/scala/debugger/SiliconDebugger.scala index 1efe421b2..83fbaecac 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.DependencyAnalysisInfos import viper.silicon.interfaces.state.Chunk import viper.silicon.interfaces.{Failure, SiliconDebuggingFailureContext, Success, VerificationResult} import viper.silicon.resources.{FieldID, PredicateID} @@ -270,7 +271,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) @@ -430,7 +431,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, DependencyAnalysisInfos.DefaultDependencyAnalysisInfos)((_, t, newE, newV) => { resT = t resE = newE.get resV = newV @@ -497,7 +498,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, DependencyAnalysisInfos.DefaultDependencyAnalysisInfos)((newS, t, newE, newV) => { resS = newS resT = t resE = newE.get diff --git a/src/main/scala/decider/CVC5ProverStdIO.scala b/src/main/scala/decider/CVC5ProverStdIO.scala index f89478c9e..740e899f2 100644 --- a/src/main/scala/decider/CVC5ProverStdIO.scala +++ b/src/main/scala/decider/CVC5ProverStdIO.scala @@ -6,12 +6,13 @@ package viper.silicon.decider -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 fbf93b066..e9bca8bc0 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -7,26 +7,29 @@ package viper.silicon.decider import com.typesafe.scalalogging.Logger -import viper.silicon.debugger.DebugExp import viper.silicon._ import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces._ import viper.silicon.interfaces.decider._ +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, _} 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.{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} import scala.collection.immutable.HashSet -import scala.reflect.{ClassTag, classTag} import scala.collection.mutable +import scala.reflect.{ClassTag, classTag} /* * Interfaces @@ -49,31 +52,39 @@ trait Decider { def pushScope(): Unit def popScope(): Unit - def checkSmoke(isAssert: Boolean = false): Boolean + def checkSmoke(analysisInfos: DependencyAnalysisInfos, isAssert: Boolean = false): Boolean - def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp])): Unit + def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), analysisInfos: DependencyAnalysisInfos): Unit def setPathConditionMark(): Mark def finishDebugSubExp(description : String): Unit 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, debugExp: Option[DebugExp]): Unit - def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]]): 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 assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean): 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 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], 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): Boolean + def check(t: Term, timeout: Int, analysisInfos: DependencyAnalysisInfos): Boolean /* 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 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 def fresh(id: String, argSorts: Seq[Sort], resultSort: Sort): Function @@ -101,6 +112,13 @@ trait Decider { def setPcs(other: PathConditionStack): Unit def statistics(): Map[String, String] + + var dependencyAnalyzer: DependencyAnalyzer + def initDependencyAnalyzer(member: Member, preambleNodes: Iterable[DependencyAnalysisNode]): Unit + def removeDependencyAnalyzer(): Unit + def getAnalysisInfo(daInfos: DependencyAnalysisInfos): AnalysisInfo + def isDependencyAnalysisEnabled: Boolean + def handleFailedAssertion(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos, assumeFailedAssertion: Boolean): Unit } /* @@ -129,6 +147,26 @@ 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 + + var dependencyAnalyzer: DependencyAnalyzer = new NoDependencyAnalyzer() + + 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) { + dependencyAnalyzer = new DefaultDependencyAnalyzer(member) + dependencyAnalyzer.addNodes(preambleNodes) + }else{ + removeDependencyAnalyzer() + } + } + + override def removeDependencyAnalyzer(): Unit = { + dependencyAnalyzer = new NoDependencyAnalyzer + } + + def getAnalysisInfo(analysisInfos: DependencyAnalysisInfos): AnalysisInfo = AnalysisInfo(this, dependencyAnalyzer, analysisInfos) def functionDecls: Set[FunctionDecl] = _declaredFreshFunctions def macroDecls: Vector[MacroDecl] = _declaredFreshMacros @@ -249,9 +287,11 @@ 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]), analysisInfos: DependencyAnalysisInfos): Unit = { + if(isPathInfeasible) return + 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, analysisInfos) } def setPathConditionMark(): Mark = pathConditions.mark() @@ -270,36 +310,67 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } + 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 = { + if(!isDependencyAnalysisEnabled) + return buildChunk(perm) + + val labelNodeOpt = getOrCreateAnalysisLabelNode() + + if(isExhale) + dependencyAnalyzer.registerExhaleChunk(sourceChunks, buildChunk, perm, labelNodeOpt, analysisInfo) + else { + dependencyAnalyzer.registerInhaleChunk(sourceChunks, buildChunk, perm, labelNodeOpt, analysisInfo) + } + } + + private def getOrCreateAnalysisLabelNode(sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Option[LabelNode] = { + if(!isDependencyAnalysisEnabled) + return None + + 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 wrapWithDependencyAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term = { + if(!isDependencyAnalysisEnabled || term.equals(True) || sourceChunks.size + sourceTerms.size == 0) + return term + + val labelNode = getOrCreateAnalysisLabelNode(sourceChunks, sourceTerms) + 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) } } - 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 = { + 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)))), false, false) + assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), enforceAssumption = false, isDefinition = false, analysisInfos) } else { - assume(assumptions=InsertionOrderedSet((t, None)), false, false) + assume(assumptions=InsertionOrderedSet((t, None)), enforceAssumption = false, isDefinition = false, analysisInfos) } } - def assume(t: Term, debugExp: Option[DebugExp]): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), false) + 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]): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), enforceAssumption=false, isDefinition=true) + def assumeDefinition(t: Term, debugExp: Option[DebugExp], analysisInfos: DependencyAnalysisInfos): Unit = { + assume(InsertionOrderedSet(Seq((t, debugExp))), enforceAssumption=false, isDefinition=true, analysisInfos) } - def assume(assumptions: Iterable[(Term, Option[DebugExp])]): Unit = - assume(InsertionOrderedSet(assumptions), false) - - def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], enforceAssumption: Boolean = false, isDefinition: Boolean = false): 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)) @@ -309,26 +380,60 @@ 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) + val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, _) => + 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]]): Unit = { - assumeWithoutSmokeChecks(InsertionOrderedSet(assumptions)) + def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], analysisInfos: DependencyAnalysisInfos): Unit = { + val assumptionsWithLabels = addAssumptionLabels(assumptions, analysisInfos) + + assumeWithoutSmokeChecks(InsertionOrderedSet(assumptionsWithLabels)) if (debugMode) { debugExps.get foreach (e => addDebugExp(e)) } } - def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean): 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 = + if (enforceAssumption) assumptions + else assumptions filterNot isKnownToBeTrue + + if(filteredTerms.isEmpty) return + + val assumptionsWithLabels = addAssumptionLabels(filteredTerms, analysisInfos) + + assumeWithoutSmokeChecks(InsertionOrderedSet(assumptionsWithLabels)) + + if (debugMode && debugExp.isDefined) { + addDebugExp(debugExp.get) + } + } + + private def addAssumptionLabels(filteredTerms: Iterable[Term], analysisInfos: DependencyAnalysisInfos) = { + filteredTerms map (t => { + val assumptionIds = dependencyAnalyzer.addAssumption(t, analysisInfos) + (t, DependencyAnalyzer.createAssumptionLabel(assumptionIds)) + }) + } + + def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, analysisInfos: DependencyAnalysisInfos): Unit = { val filteredTerms = if (enforceAssumption) terms else terms filterNot isKnownToBeTrue - if (debugMode && filteredTerms.nonEmpty) { + if(filteredTerms.isEmpty) return + + if (debugMode) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) } - - if (filteredTerms.nonEmpty) assumeWithoutSmokeChecks(InsertionOrderedSet(filteredTerms)) + val termsWithLabel = addAssumptionLabels(filteredTerms, analysisInfos) + assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) } def debuggerAssume(terms: Iterable[Term], de: DebugExp) = { @@ -341,7 +446,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => }) } - private def assumeWithoutSmokeChecks(terms: InsertionOrderedSet[Term], isDefinition: Boolean = false) = { + private def assumeWithoutSmokeChecks(termsWithLabel: InsertionOrderedSet[(Term, String)], isDefinition: Boolean = false): None.type = { + if (isPathInfeasible) return None + + val terms = termsWithLabel map (_._1) val assumeRecord = new DeciderAssumeRecord(terms) val sepIdentifier = symbExLog.openScope(assumeRecord) @@ -353,26 +461,79 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } /* Add terms to the prover's assumptions */ - terms foreach prover.assume + termsWithLabel foreach { case (t, label) => prover.assume(t, label) } symbExLog.closeScope(sepIdentifier) None } + def assumeLabel(term: Term, assumptionLabel: String): Unit = { + pathConditions.addAnalysisLabel(term) + prover.assume(term, assumptionLabel) + } + /* Asserting facts */ - def checkSmoke(isAssert: Boolean = false): Boolean = { + def checkSmoke(analysisInfos: DependencyAnalysisInfos, isAssert: Boolean=false): Boolean = { + + 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 - prover.check(timeout) == Unsat + val result = prover.check(timeout, label) == Unsat + + if(result){ + 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){ + checkNode foreach (node => dependencyAnalyzer.addAssertionNode(node.getAssertFailedNode)) + } + result + } + + 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 => checkSmoke(analysisInfos) + case _ => + } + } } - def check(t: Term, timeout: Int): Boolean = deciderAssert(t, Some(timeout)) + 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 assert(t: Term, timeout: Option[Int] = Verifier.config.assertTimeout.toOption) - (Q: Boolean => VerificationResult) - : VerificationResult = { + def check(t: Term, timeout: Int, analysisInfos: DependencyAnalysisInfos): Boolean = { + deciderAssert(t, analysisInfos, Some(timeout), isCheck=true)._1 + } + + + def assert(t: Term, analysisInfos: DependencyAnalysisInfos)(Q: Boolean => VerificationResult): VerificationResult = assert(t, analysisInfos, timeout=Verifier.config.assertTimeout.toOption)(Q) - val success = deciderAssert(t, 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 @@ -386,15 +547,22 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => Q(success) } - private def deciderAssert(t: Term, timeout: Option[Int]) = { + 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 = isKnownToBeTrue(t) - val result = asserted || proverAssert(t, timeout) + val asserted = if(isDependencyAnalysisEnabled) t.equals(True) else isKnownToBeTrue(t) + + val assertNode = if(!asserted) dependencyAnalyzer.createAssertOrCheckNode(t, analysisInfos, isCheck) else None + + val result = asserted || proverAssert(t, timeout, DependencyAnalyzer.createAssertionLabel(assertNode map (_.id))) + + if(result) { + assertNode foreach dependencyAnalyzer.addAssertionNode + } symbExLog.closeScope(sepIdentifier) - result + (result, assertNode) } private def isKnownToBeTrue(t: Term) = t match { @@ -405,11 +573,14 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => case _ => false } - 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 = isPathInfeasible || prover.assert(t, timeout, 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())) @@ -527,7 +698,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/PathConditions.scala b/src/main/scala/decider/PathConditions.scala index 31f3f7ea4..509ebde34 100644 --- a/src/main/scala/decider/PathConditions.scala +++ b/src/main/scala/decider/PathConditions.scala @@ -31,6 +31,8 @@ trait RecordedPathConditions { def definingAssumptions: InsertionOrderedSet[Term] def definingAssumptionExps: InsertionOrderedSet[DebugExp] def declarations: InsertionOrderedSet[Decl] + def analysisLabels: InsertionOrderedSet[Term] + def infeasibilityNodeId: Option[Int] def definitionsOnly: RecordedPathConditions @@ -68,6 +70,9 @@ 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 addAnalysisLabel(assumption: Term): Unit def startDebugSubExp(): Unit def finishDebugSubExp(description : String): Unit @@ -91,6 +96,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 @@ -100,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 @@ -112,6 +119,12 @@ 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 infeasibilityNodeId: Option[Int] = _infeasibilityNodeId + def setInfeasibilityNodeId(id: Option[Int]): Unit = { + _infeasibilityNodeId = id + } def assumptions: InsertionOrderedSet[Term] = globalAssumptions ++ nonGlobalAssumptions def assumptionDebugExps: InsertionOrderedSet[DebugExp] = globalAssumptionDebugExps ++ nonGlobalAssumptionDebugExps @@ -130,6 +143,7 @@ private class PathConditionStackLayer result._globalDefiningAssumptionDebugExps = _globalDefiningAssumptionDebugExps result._nonGlobalAssumptionDebugExps = _nonGlobalDefiningAssumptionDebugExps result._nonGlobalDefiningAssumptionDebugExps = _nonGlobalDefiningAssumptionDebugExps + result._analysisLabels = _analysisLabels result } @@ -162,6 +176,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 } @@ -257,9 +280,16 @@ 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 infeasibilityNodeId(layers: Stack[PathConditionStackLayer]): Option[Int] = + layers.flatMap(_.infeasibilityNodeId).headOption + protected def contains(layers: Stack[PathConditionStackLayer], assumption: Term): Boolean = layers exists (_.contains(assumption)) + protected def conditionalized(layers: Stack[PathConditionStackLayer]): Seq[Term] = { var unconditionalTerms = Vector.empty[Term] var conditionalTerms = Vector.empty[Term] @@ -274,8 +304,7 @@ private trait LayeredPathConditionStackLike { case None => } - conditionalTerms :+= - Implies(implicationLHS, And(layer.nonGlobalAssumptions)) + conditionalTerms :+= Implies(implicationLHS, And(layer.nonGlobalAssumptions)) } unconditionalTerms ++ conditionalTerms @@ -402,6 +431,8 @@ 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) + val infeasibilityNodeId: Option[Int] = infeasibilityNodeId(from) def contains(assumption: Term): Boolean = contains(from, assumption) @@ -459,6 +490,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() } @@ -501,6 +537,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 @@ -568,6 +608,10 @@ private[decider] class LayeredPathConditionStack def declarations: InsertionOrderedSet[Decl] = InsertionOrderedSet(layers.flatMap(_.declarations)) // Note: Performance? + 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/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index d64fbddd7..6c580cf90 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -6,21 +6,21 @@ package viper.silicon.decider -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.interfaces.decider.{Prover, Result, Sat, Unknown, Unsat} +import viper.silicon.dependencyAnalysis.DependencyAnalyzer +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, @@ -39,10 +39,12 @@ 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 = _ var lastModel : String = _ + protected var lastUnsatCore_ : String = _ def exeEnvironmentalVariable: String def dependencies: Seq[SilDefaultDependency] @@ -220,6 +222,10 @@ abstract class ProverStdIO(uniqueId: String, // private val quantificationLogger = bookkeeper.logfiles("quantification-problems") def assume(term: Term): Unit = { + assume(term, nextProverLabel()) + } + + 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,26 +239,37 @@ abstract class ProverStdIO(uniqueId: String, // problems.foreach(p => quantificationLogger.println(s" $p")) // } // }) + val finalLabel = if(label.isEmpty) nextProverLabel() else label + assume(termConverter.convert(term), finalLabel) + } - assume(termConverter.convert(term)) + def nextProverLabel(): String = { + val label = "prover_" + proverLabelId + proverLabelId += 1 + label } - def assume(term: String): Unit = { + def assume(term: String, label: String): Unit = { // bookkeeper.assumptionCounter += 1 - writeLine("(assert " + term + ")") + if((Verifier.config.enableDependencyAnalysis() && label.nonEmpty) || Verifier.config.enableUnsatCores()){ + writeLine("(assert (! " + term + " :named " + (if(label.nonEmpty) label else nextProverLabel()) + "))") + }else{ + writeLine("(assert " + term + ")") + } + 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)}") @@ -261,11 +278,16 @@ 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 + "))") + 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 + "))") + } + readSuccess() val startTime = System.currentTimeMillis() @@ -276,6 +298,8 @@ abstract class ProverStdIO(uniqueId: String, if (!result) { retrieveAndSaveModel() retrieveReasonUnknown() + }else if(Verifier.config.enableDependencyAnalysis()){ + lastUnsatCore_ = extractUnsatCore() } pop() @@ -283,6 +307,15 @@ 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 getLastUnsatCore: String = lastUnsatCore_ + def saturate(data: Option[Config.ProverStateSaturationTimeout]): Unit = { data match { case Some(Config.ProverStateSaturationTimeout(timeout, comment)) => saturate(timeout, comment) @@ -348,16 +381,21 @@ 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.enableDependencyAnalysis()) + lastUnsatCore_ = extractUnsatCore() + + result } def statistics(): Map[String, String] = { diff --git a/src/main/scala/decider/Z3ProverAPI.scala b/src/main/scala/decider/Z3ProverAPI.scala index a9effd0c7..dce2df33c 100644 --- a/src/main/scala/decider/Z3ProverAPI.scala +++ b/src/main/scala/decider/Z3ProverAPI.scala @@ -6,23 +6,21 @@ package viper.silicon.decider +import com.microsoft.z3._ +import com.microsoft.z3.enumerations.Z3_param_kind import com.typesafe.scalalogging.LazyLogging 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.reporting.ExternalToolError -import viper.silicon.reporting.ProverInteractionFailed - import scala.jdk.CollectionConverters.MapHasAsJava import scala.util.Random @@ -263,6 +261,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) @@ -294,7 +296,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 { @@ -389,7 +392,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) @@ -402,6 +405,8 @@ class Z3ProverAPI(uniqueId: String, } } + def getLastUnsatCore: String = "" // TODO ake + def endPreamblePhase(): Unit = { if (!preamblePhaseOver) { preamblePhaseOver = true diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala new file mode 100644 index 000000000..60954ec5c --- /dev/null +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -0,0 +1,10 @@ +package viper.silicon.dependencyAnalysis + + +import viper.silicon.decider.Decider +import viper.silver.dependencyAnalysis.DependencyType + +case class AnalysisInfo(decider: Decider, dependencyAnalyzer: DependencyAnalyzer, analysisInfos: DependencyAnalysisInfos) { + def withDependencyType(dependencyType: DependencyType): AnalysisInfo = this.copy(analysisInfos=analysisInfos.withDependencyType(dependencyType)) +} + diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala new file mode 100644 index 000000000..2cee072d5 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala @@ -0,0 +1,136 @@ +package viper.silicon.dependencyAnalysis + +import viper.silicon.SiliconRunner +import viper.silicon.verifier.Verifier +import viper.silver.ast +import viper.silver.ast._ +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], analysisEnabled: Boolean = true) { + + private def isAnalysisEnabled = Verifier.config.enableDependencyAnalysis() && analysisEnabled + + def addInfo(info: ast.Info, node: ast.Node): DependencyAnalysisInfos = { + if(!isAnalysisEnabled) return this + + 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): DependencyAnalysisInfos = { + if(!isAnalysisEnabled) return this + + 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): DependencyAnalysisInfos = { + 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 + + this.copy(dependencyTypes = DependencyTypeInfo(dependencyType) +: dependencyTypes) + } + + def withSource(source: AnalysisSourceInfo): DependencyAnalysisInfos = { + if(!isAnalysisEnabled) return this + + this.copy(sourceInfos = source +: sourceInfos) + } + + private def getNodeInfo(n: ast.Node): String = { + n match { + case np: Positioned => + s"${n.toString()} (${np.pos})" + case _ => + s"${n.toString()} (???)" + } + } + + 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 = { + if(!isAnalysisEnabled) return StringAnalysisSourceInfo("Unknown", NoPosition) + 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 = { + if(!isAnalysisEnabled) return DependencyType.make(AssumptionType.Unknown) + 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 = { + 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 + } + } + + def withMergeInfo(mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfos = { + if(!isAnalysisEnabled) return this + + this.copy(mergeInfos = mergeInfo +: mergeInfos) + } + + def withJoinInfo(joinInfo: DependencyAnalysisJoinInfo): DependencyAnalysisInfos = { + if(!isAnalysisEnabled) return this + + this.copy(joinInfos = joinInfo +: joinInfos) + } + + def withEnabled(analysisEnabled: Boolean): DependencyAnalysisInfos = this.copy(analysisEnabled=analysisEnabled) +} + +object DependencyAnalysisInfos { + val DefaultDependencyAnalysisInfos = DependencyAnalysisInfos(List.empty, List.empty, List.empty, 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): DependencyAnalysisInfos = + DependencyAnalysisInfos(List(sourceInfo), List(DependencyTypeInfo(dependencyType)), List.empty, List.empty, List.empty) + + def create(infoString: String, dependencyType: DependencyType, mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfos = + create(StringAnalysisSourceInfo(infoString, NoPosition), dependencyType, mergeInfo) + + 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) +} \ No newline at end of file diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala new file mode 100644 index 000000000..d471da068 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -0,0 +1,153 @@ +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.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silver.dependencyAnalysis._ + + +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 + + + /** + * 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 + + /** + * 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] + + /** + * The assumes or asserted Silicon term. Currently, only used for debugging purposes. + */ + val term: Term + def getTerm: Term = term + + 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). + */ + override def toString: String = id.toString + " | " + getNodeString + " | " + sourceInfo.toString + def getNodeString: String + def getNodeType: String + + // TODO ake: remove workaround + override def hashCode(): Int = + 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 { + override def getNodeType: String = "Assumption" + +} +trait GeneralAssertionNode extends DependencyAnalysisNode { + override def getNodeType: String = "Assertion" + + val hasFailed: Boolean + + 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 + val labelNode: LabelNode +} + +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, 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, 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) +} + +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) +} + +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, 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, joinInfos, 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, _id: Option[Int]=None) extends GeneralAssumptionNode { + val sourceInfo: AnalysisSourceInfo = NoAnalysisSourceInfo() + val assumptionType: AssumptionType = AssumptionType.Internal + val mergeInfo: DependencyAnalysisMergeInfo = NoDependencyAnalysisMerge() + val description: String = term.toString + val joinInfos: List[SimpleDependencyAnalysisJoin] = List.empty + override def getNodeType: String = "Label" + 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, assumptionType: AssumptionType, _id: Option[Int]=None) extends GeneralAssumptionNode { + val term: Term = False + val mergeInfo: DependencyAnalysisMergeInfo = NoDependencyAnalysisMerge() + val description: String = "False" + val joinInfos: List[SimpleDependencyAnalysisJoin] = List.empty + + override def getNodeType: String = "Infeasible" + override def getNodeString: String = "infeasible" +} diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisReporter.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisReporter.scala new file mode 100644 index 000000000..ca906ac06 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisReporter.scala @@ -0,0 +1,11 @@ +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 { + 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 new file mode 100644 index 000000000..68a04512a --- /dev/null +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala @@ -0,0 +1,13 @@ +package viper.silicon.dependencyAnalysis + +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter +import viper.silver.ast.Program + +case class DependencyAnalysisResult(programName: String, program: Program, dependencyGraphInterpreters: Set[DependencyGraphInterpreter[IntraProcedural]]){ + + protected lazy val fullDependencyGraphInterpreter: DependencyGraphInterpreter[Final] = + DependencyAnalyzer.joinGraphsAndGetInterpreter(programName, dependencyGraphInterpreters) + + def getFullDependencyGraphInterpreter: DependencyGraphInterpreter[Final] = fullDependencyGraphInterpreter + +} diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala new file mode 100644 index 000000000..aa29facde --- /dev/null +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -0,0 +1,412 @@ +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 +import viper.silver.ast +import viper.silver.ast._ +import viper.silver.dependencyAnalysis.JoinType.JoinType +import viper.silver.dependencyAnalysis.{DependencyAnalysisMergeInfo, EdgeType, EvalStackDependencyAnalysisJoin, JoinType} + +import scala.collection.mutable + + +trait DependencyAnalyzer { + protected val dependencyGraph: DependencyGraph[Init] = new DependencyGraph() + + def getMember: Option[ast.Member] + + def getNodes: Iterable[DependencyAnalysisNode] + + def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit + def addAssertionNode(node: GeneralAssertionNode): Unit + def addAssumptionNode(node: GeneralAssumptionNode): Unit + 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, 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 + + /** + * 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], analysisInfos: DependencyAnalysisInfos): Unit + + /** + * 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) intraprocedural dependencies + */ + def buildFinalGraph(): Option[DependencyGraph[IntraProcedural]] + + /** + * 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 +} + +object DependencyAnalyzer { + val analysisLabelName: String = "$$analysisLabel$$" + 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 extractEnableAnalysisFromInfo(info: ast.Info): Option[Boolean] = { + val annotation = extractAnnotationFromInfo(info, enableDependencyAnalysisAnnotationKey) + if(annotation.isDefined && annotation.get.nonEmpty) annotation.get.head.toBooleanOption else None + } + + def createAssumptionLabel(id: Option[Int]): String = { + createLabel("assumption", id) + } + + 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]): String = { + if (id.isDefined) description + "_" + id.get + else "" + } + + def getIdFromLabel(label: String): Int = { + label.split("_")(1).toInt + } + + /** + * + * @param name Optional name for the result graph. + * @param dependencyGraphInterpreters The graphs to be joined. + * @return A dependency graph interpreter operating on a new dependency graph that represents all input graphs and + * 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. + */ + 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)) + 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) + + def getJoinNodesByJoinInfo(candidateNodes: Set[DependencyAnalysisNode], joinType: JoinType) = { + candidateNodes + .flatMap(node => node.joinInfos.filter(_.joinType.equals(joinType)).map((_, node))) + .groupBy(_._1) + .view.mapValues(_.map(_._2)) + .toMap + } + + 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)) + } + + val newInterpreter = new DependencyGraphInterpreter[Final](name, newGraph, dependencyGraphInterpreters.toList.flatMap(_.getErrors)) + newInterpreter + } +} + +class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { + 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 + + private def getNodeIdsByTerm(terms: Set[Term]): Set[Int] = { + dependencyGraph.getNodes + .filter(t => terms.contains(t.getTerm)) + .map(_.id).toSet + } + + + override def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit = { + nodes foreach dependencyGraph.addNode + } + + override def addAssumptionNode(node: GeneralAssumptionNode): Unit = dependencyGraph.addAssumptionNode(node) + + override def addAssertionNode(node: GeneralAssertionNode): Unit = dependencyGraph.addAssertionNode(node) + + 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, 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) + } + + 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.analysisInfos, labelNode) + 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 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)) + chunk + } + + 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, 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) + } + + override def createLabelNode(label: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = { + val labelNode = LabelNode(label) + addAssumptionNode(labelNode) + dependencyGraph.addEdges(getNodeIdsByTerm(sourceTerms.toSet), labelNode.id) + Some(labelNode) + } + + override def createAssertOrCheckNode(term: Term, analysisInfos: DependencyAnalysisInfos, isCheck: Boolean): Option[GeneralAssertionNode] = { + 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)) + } + + private 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, analysisInfos: DependencyAnalysisInfos): Option[Int] = { + val node = createAssertOrCheckNode(False, analysisInfos, isCheck) + addAssertionNode(node.get) + node.map(_.id) + } + + 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, analysisInfos: DependencyAnalysisInfos): Option[Int] = { + 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) + Some(assertFailedNode.id) + } + + + override def addDependency(source: Option[Int], dest: Option[Int]): Unit = { + if(source.isDefined && dest.isDefined) + dependencyGraph.addEdges(source.get, Set(dest.get)) + } + + override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { + val assumptionLabels = dep.replace("(", "").replace(")", "").split(" ") + 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], 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) + } + + override def addCustomDependenciesBetweenMergeInfos(sourceExps: Seq[Exp], targetExps: Seq[Exp]): Unit = { + 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(mergedGraph: DependencyGraph[IntraProcedural]): Unit = { + customMergeDependencies.foreach{ case (sourceMergeInfos, targetMergeInfos) => + 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) + } + } + + /** + * + * @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[IntraProcedural]] = { + dependencyGraph.removeLabelNodes() + val mergedGraph = if(Verifier.config.enableDependencyAnalysisDebugging()) dependencyGraph.asInstanceOf[DependencyGraph[IntraProcedural]] else buildAndGetMergedGraph() + addTransitiveEdges(mergedGraph) + if(!Verifier.config.enableDependencyAnalysisDebugging()) mergedGraph.removeInternalNodes() + 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) => + 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? + } + + addCustomMergeDependencies(mergedGraph) + } + + /** + * 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[IntraProcedural] = { + def keepNode(n: DependencyAnalysisNode): Boolean = !n.mergeInfo.isMerge || n.joinInfos.nonEmpty || n.isInstanceOf[InfeasibilityNode] || n.isInstanceOf[AxiomAssumptionNode] + + val mergedGraph = new DependencyGraph[IntraProcedural] + val nodeMap = mutable.HashMap[Int, Int]() + + dependencyGraph.getAssumptionNodes.filter(keepNode).foreach { n => + nodeMap.put(n.id, n.id) + mergedGraph.addAssumptionNode(n) + } + 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, joinInfos) + assumptionNodes foreach (n => nodeMap.put(n.id, newNode.id)) + mergedGraph.addAssumptionNode(newNode) + } + } + + 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, n.mergeInfo, n.joinInfos)) + assertionNodesBySource foreach { case ((sourceInfo, assumptionType, mergeInfo, joinInfos), assertionNodes) => + if (assertionNodes.nonEmpty) { + val newNode = SimpleAssertionNode(True, sourceInfo, assumptionType, mergeInfo, joinInfos, hasFailed=assertionNodes.exists(_.hasFailed)) + assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) + mergedGraph.addAssertionNode(newNode) + } + } + + dependencyGraph.getAllEdges foreach { case (target, deps) => + val newTarget = nodeMap.getOrElse(target, target) + mergedGraph.addEdges(deps.map(d => nodeMap.getOrElse(d, d)), newTarget) + } + + mergedGraph + } + + /** + * 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], analysisInfos: DependencyAnalysisInfos): Unit = { + val newAssertionNodeId = addAssertNode(False, analysisInfos) + addDependency(infeasNodeId, newAssertionNodeId) + } + +} + +/** + * This DependencyAnalyzer implementation is used by default and does nothing. + */ +class NoDependencyAnalyzer extends DependencyAnalyzer { + + override def getMember: Option[ast.Member] = None + + override def getNodes: Iterable[DependencyAnalysisNode] = Set.empty + + override def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit = {} + override def addAssertionNode(node: GeneralAssertionNode): Unit = {} + override def addAssumptionNode(node: GeneralAssumptionNode): Unit = {} + 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, 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], analysisInfos: DependencyAnalysisInfos): Unit = {} + + override def buildFinalGraph(): Option[DependencyGraph[IntraProcedural]] = None + + override def addCustomDependenciesBetweenMergeInfos(sourceExps: Seq[Exp], targetExps: Seq[Exp]): Unit = {} + +} diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala new file mode 100644 index 000000000..1b41246df --- /dev/null +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -0,0 +1,298 @@ +package viper.silicon.dependencyAnalysis + +import viper.silver.dependencyAnalysis.AssumptionType + +import java.io.PrintWriter +import java.nio.file.Paths +import java.util.concurrent.atomic.AtomicInteger +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() + } +} + +trait DependencyGraphState +class Init extends DependencyGraphState +class IntraProcedural extends DependencyGraphState +class Final extends DependencyGraphState + +trait ReadOnlyDependencyGraph[T <: DependencyGraphState] { + def getNodes: Seq[DependencyAnalysisNode] + def getAssumptionNodes: Seq[GeneralAssumptionNode] + def getAssertionNodes: Seq[GeneralAssertionNode] + + /** + * @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 +} + +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 + 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 + def getAssumptionNodes: Seq[GeneralAssumptionNode] = assumptionNodes.toSeq + def getAssertionNodes: Seq[GeneralAssertionNode] = assertionNodes.toSeq + def getDirectEdges: Map[Int, Set[Int]] = edges.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 + val intraMethodEdges = mutable.Map[Int, Set[Int]]() + keys foreach {key => + intraMethodEdges.update(key, edges.getOrElse(key, Set())) + } + intraMethodEdges.toMap + } + + def getAllEdges: Map[Int, Set[Int]] = { + val intraMethodEdges = getIntraMethodEdges + 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()) ++ edgesConnectingMethodsDownwards.getOrElse(key, Set()) ++ edgesConnectingMethodsUpwards.getOrElse(key, Set())) + } + allEdges.toMap + } + + 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 getVacuousProofs: Set[Int] = vacuousProofs.toSet // TODO ake: what to do with these? + + def addAssumptionNode(node: GeneralAssumptionNode): Unit = { + assumptionNodes = assumptionNodes :+ node + } + + def addAssumptionNodes(newNodes: Iterable[GeneralAssumptionNode]): Unit = { + assumptionNodes = assumptionNodes ++ newNodes + } + + def addNode(node: DependencyAnalysisNode): Unit = { + node match { + case node: GeneralAssertionNode => addAssertionNode(node) + case node: GeneralAssumptionNode => addAssumptionNode(node) + case _ => assert(false) + } + } + + def addAssertionNode(node: GeneralAssertionNode): Unit = { + assertionNodes = assertionNodes :+ node + } + + def addAssertionNodes(newNodes: Iterable[GeneralAssertionNode]): Unit = { + assertionNodes = assertionNodes ++ newNodes + } + + def addEdges(source: Int, targets: Iterable[Int]): Unit = { + addEdges(Set(source), targets) + } + + def addEdges(sources: Iterable[Int], target: Int): Unit = { + 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 = { + targets foreach (addEdges(sources, _)) + } + + 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) + edgesConnectingMethodsUpwards.update(target, oldSources ++ newSources) + } + + def addEdgesConnectingMethodsUpwards(sources: Iterable[Int], targets: Iterable[Int]): Unit = { + targets foreach (addEdgesConnectingMethodsUpwards(sources, _)) + } + + def addEdgesConnectingMethodsUpwards(source: Int, targets: Iterable[Int]): Unit = { + addEdgesConnectingMethodsUpwards(Set(source), targets) + } + + + def addVacuousProof(assertionId: Int): Unit = { + vacuousProofs = assertionId +: vacuousProofs + } + + 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 = getAllEdges(includeDownwardEdges, includeUpwardEdges) + while(queue.nonEmpty){ + val curr = queue.head + val newVisits = allEdges.getOrElse(curr, Set()).diff(infeasibilityNodeIds) + visited = visited ++ Set(curr) + queue = queue.tail ++ newVisits.diff(visited) + } + visited + } + + 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(includeDownwardEdges, includeUpwardEdges) + 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 + } + + /** + * 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 + val successors = edges.getOrElse(id, Set.empty) + edges.remove(id) + predecessors foreach (pid => edges.update(pid, edges.getOrElse(pid, Set.empty).filter(_ != id) ++ successors)) + } + + + /** + * 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] + + assumptionNodes filter filterCriteria foreach removeAllEdgesForNode + 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) && !n.isInstanceOf[InfeasibilityNode] + } + + assumptionNodes filter filterCriteria foreach removeAllEdgesForNode + assumptionNodes = assumptionNodes filterNot filterCriteria + } + + def exportGraph(dirName: String): Unit = { + val directory = Paths.get(dirName).toFile + directory.mkdir() + exportNodes(dirName + "/nodes.csv") + exportEdges(dirName + "/edges.csv") + } + + 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"))) + 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") + writer.println(builder.toString()) + 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.mergeInfo.toString, node.sourceInfo.getDescription) + parts.map(_.replace("#", "@")).mkString(sep) + } + 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")) + + val writer = new PrintWriter(fileName) + writer.println(headerParts.mkString(sep)) + writer.println(builder.result()) + writer.close() + } +} + + diff --git a/src/main/scala/dependencyAnalysis/README.md b/src/main/scala/dependencyAnalysis/README.md new file mode 100644 index 000000000..b03c23dc4 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/README.md @@ -0,0 +1,87 @@ +# Dependency Analysis + + +# Running Silicon with Dependency Analysis + +Dependency Analysis is enabled thorough the following configuration options: +`--enableDependencyAnalysis --disableInfeasibilityChecks --proverArgs "proof=true unsat-core=true"` + +Additionally, to retrieve the results and query the dependency graph, use: +- `--startDependencyAnalysisTool` + - Automatically starts the command-line tool once verification terminates. +- `--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 `--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 `--startDependencyAnalysisTool`. + +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` +- `progress` + - Prints the verification progress of the program +- `guide` + - Prints verification guidance + + +# Neo4j Scripts and Usage + +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: + +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/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! + +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`. + +# 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. diff --git a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala new file mode 100644 index 000000000..4c1b2a0cb --- /dev/null +++ b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala @@ -0,0 +1,63 @@ +package viper.silicon.dependencyAnalysis + +import viper.silicon.state.terms.{And, Term} +import viper.silver.ast.Position +import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, StringAnalysisSourceInfo} + +object UserLevelDependencyAnalysisNode { + + def from(dependencyNodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = { + 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 + } + + def extractByAssumptionType(nodes: Set[UserLevelDependencyAnalysisNode], assumptionTypes: Set[AssumptionType]): Set[UserLevelDependencyAnalysisNode] = { + nodes.filter(node => assumptionTypes.intersect(node.assumptionTypes).nonEmpty) + } + + def mkUserLevelString(nodes: Set[DependencyAnalysisNode], sep: String = "\n"): String = { + from(nodes).toList.sortBy(n => (n.source.getLineNumber, n.source.toString)).mkString(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]) { + + 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)) + + lazy val hasFailures: Boolean = lowerLevelNodes.filter(_.isInstanceOf[GeneralAssertionNode]).map(_.asInstanceOf[GeneralAssertionNode]).exists(_.hasFailed) + + + override def toString: String = source.toString + + def groupingCondition: (String, Position) = (source.toString, position) + +} + +case class CompactUserLevelDependencyAnalysisNode(source: AnalysisSourceInfo, assumptionTypes: Set[AssumptionType], assertionTypes: Set[AssumptionType], hasFailures: Boolean) { + def position: Position = source.getPosition +} diff --git a/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala b/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala new file mode 100644 index 000000000..6dd1afc83 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala @@ -0,0 +1,54 @@ +package viper.silicon.dependencyAnalysis.cliTool + +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter +import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, Final, UserLevelDependencyAnalysisNode} + +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..72692ce0f --- /dev/null +++ b/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala @@ -0,0 +1,249 @@ +package viper.silicon.dependencyAnalysis.cliTool + +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter +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} + +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..34597c259 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala @@ -0,0 +1,70 @@ +package viper.silicon.dependencyAnalysis.cliTool + +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 AssertionTypesCommand, + 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 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 => + 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..aebd16450 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala @@ -0,0 +1,201 @@ +package viper.silicon.dependencyAnalysis.cliTool + +import viper.silicon.dependencyAnalysis._ +import viper.silicon.dependencyAnalysis.graphInterpretation.{DependencyAnalysisProgressSupporter, DependencyGraphInterpreter} +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 + +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 TestDependencyAnalysisCliExtension(fullGraphInterpreter), + new BenchmarkDependencyAnalysisCliExtension(fullGraphInterpreter, 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'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(inputParts.tail) + 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 + + 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.") + } + + 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)) + + 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 = { + 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.") + } + + 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.") + } + + private def handleVerificationGuidanceQuery(): Unit = { + + 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: ") + 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/cliTool/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala new file mode 100644 index 000000000..bbb88a128 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala @@ -0,0 +1,183 @@ +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 +import viper.silicon.state.terms.{NoPerm, Term, True, Var} +import viper.silver.ast +import viper.silver.ast._ +import viper.silver.dependencyAnalysis.{AssumptionType, SimpleDependencyAnalysisJoin, SimpleDependencyAnalysisMerge, StringAnalysisSourceInfo} +import viper.silver.frontend.SilFrontend + +import java.nio.file.Paths +import scala.io.Source + +object DependencyGraphImporter { + + private 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 = { + 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(graphFolder) + + val interpreter = new DependencyGraphInterpreter[Final]("test", graph, List.empty, None) + val userTool = new DependencyAnalysisCliTool(interpreter, Seq.empty, program, List.empty) + + 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.") + } + + 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 + + if(cmds.isEmpty) + userTool.run() + else + cmds.get foreach {c => + println(s"\n--------\nProcessing command \"$c\"...") + userTool.run(c) + } + } + + + private def importGraphFromCsv(csvFilePath: String): ReadOnlyDependencyGraph[Final] = { + val graph = new DependencyGraph[Final]() + createNodesFromCsv(graph, csvFilePath) + createEdgesFromCsv(graph, csvFilePath) + graph + } + + private def createNodesFromCsv(graph: DependencyGraph[Final], 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 mergeInfo: SimpleDependencyAnalysisMerge = SimpleDependencyAnalysisMerge(sourceInfo) + val labelNode: LabelNode = dummyLabelNode + 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, joinNodeInfos, _id=nodeId) + case "Axiom" => AxiomAssumptionNode(term, description, sourceInfo, assumptionType, mergeInfo, joinNodeInfos, _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, _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() + } + + private def createEdgesFromCsv(graph: DependencyGraph[Final], 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.addEdgesConnectingMethodsDownwards(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 + } + + private 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 +} 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 new file mode 100644 index 000000000..3f3eef4f6 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala @@ -0,0 +1,247 @@ +package viper.silicon.dependencyAnalysis.graphInterpretation + +import viper.silicon.dependencyAnalysis._ +import viper.silicon.dependencyAnalysis.graphInterpretation.DATraversalMode.DATraversalMode +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(enableDebugging: Boolean=false): (Double, Double) = { + 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 + } + + 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 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)) + 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]] = { + val excludedAssertionTypes = AssumptionType.importedTypes ++ AssumptionType.preconditionTypes + sourceToAssertionNodesMap.filter(ass => ass._2.map(_.assumptionType).intersect(excludedAssertionTypes).isEmpty) + } + + /** + * @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) = { + + // compute all dependencies of each proof obligation + val allAssertions = getAssertionsRelevantForProgress.keySet.toList + val allDependenciesPerAssertionNode = allAssertions map (ass => ({ + val ups = deps((ass, DATraversalMode.Upwards)) + val downs = deps((ass, DATraversalMode.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")}\n" + + 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) + } + + + /** + * 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) + 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")}\n" + + 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(getAssertionsRelevantForProgress.values.flatten) + + 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.extractByAssumptionType(assumptions, AssumptionType.explicitAssumptionTypes) + 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() + } + + /** + * Prints all uncovered source code statements and returns the number of uncovered source code statements. + */ + def computeUncoveredStatements(): Int = { + 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) + 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) + } +} diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisPruningSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisPruningSupporter.scala new file mode 100644 index 000000000..937634f61 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisPruningSupporter.scala @@ -0,0 +1,121 @@ +package viper.silicon.dependencyAnalysis.graphInterpretation + +import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, 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]) { + + /** + * 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 = { + 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 + } + + /** + * 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) + + 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/graphInterpretation/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala new file mode 100644 index 000000000..4017ffe24 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala @@ -0,0 +1,166 @@ +package viper.silicon.dependencyAnalysis.graphInterpretation + +import viper.silicon.dependencyAnalysis._ +import viper.silicon.interfaces.Failure +import viper.silicon.verifier.Verifier +import viper.silver.ast +import viper.silver.ast.Program +import viper.silver.dependencyAnalysis.{AssumptionType, JoinType} + +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) { + val pruningSupporter: DependencyAnalysisPruningSupporter[T] = new DependencyAnalysisPruningSupporter[T](this) + val progressSupporter: DependencyAnalysisProgressSupporter[T] = new DependencyAnalysisProgressSupporter[T](this) + + + 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 + + // 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))) + + 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)) + } + + 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 + } + + 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 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 allDeps = getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes) + getNonInternalAssertionNodes.filter(node => allDeps.contains(node.id)) + } + + def getAllExplicitDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { + val allDeps = getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes) + getExplicitAssertionNodes.filter(node => allDeps.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 // TODO ake: find a better way to include the join nodes + ) + + def getExplicitAssumptionNodes: Set[DependencyAnalysisNode] = getNonInternalAssumptionNodes filter (node => + node.isInstanceOf[GeneralAssumptionNode] && AssumptionType.explicitAssumptionTypes.contains(node.assumptionType) + ) + + 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)) + } + + // 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) + 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/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala new file mode 100644 index 000000000..d6b21735b --- /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"Node type tests: 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.diff(AssumptionType.internalTypes).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.diff(AssumptionType.internalTypes).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"Dependency tests: 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/dependencyAnalysis/neo4j_importer.py b/src/main/scala/dependencyAnalysis/neo4j_importer.py new file mode 100644 index 000000000..fdb8adb54 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/neo4j_importer.py @@ -0,0 +1,206 @@ + +import os +import pandas as pd +from neo4j import GraphDatabase +from neo4j import Transaction + +URI = "neo4j+ssc://27de0942.databases.neo4j.io" +AUTH = ("27de0942", 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="27de0942") +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.`merge info` = row.`merge info` + }}; + """ + , 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; + """) + 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") + 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; + """) + +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() diff --git a/src/main/scala/dependencyAnalysis/neo4j_query_saved_cypher_2025-9-17.csv b/src/main/scala/dependencyAnalysis/neo4j_query_saved_cypher_2025-9-17.csv new file mode 100644 index 000000000..0b64c5e9c --- /dev/null +++ b/src/main/scala/dependencyAnalysis/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 diff --git a/src/main/scala/interfaces/Verification.scala b/src/main/scala/interfaces/Verification.scala index 80aada5ea..7ead45d1c 100644 --- a/src/main/scala/interfaces/Verification.scala +++ b/src/main/scala/interfaces/Verification.scala @@ -6,11 +6,13 @@ package viper.silicon.interfaces -import viper.silicon.debugger.{DebugAxiom, DebugExp} import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.{DebugAxiom, DebugExp} +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.{BooleanLiteral, FunctionDecl, IntLiteral, MacroDecl, Term, Var} +import viper.silicon.state.terms._ import viper.silicon.state.{State, Store} import viper.silicon.verifier.Verifier import viper.silver.ast @@ -30,6 +32,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[IntraProcedural]] = 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 8c881b1e2..d4e513afe 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -6,13 +6,15 @@ package viper.silicon.interfaces.decider -import viper.silicon.debugger.DebugAxiom 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.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.verifier.Model sealed abstract class Result @@ -24,6 +26,9 @@ object Unknown extends Result 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)()) + else new NoDependencyAnalyzer() def emit(content: String): Unit def emit(contents: Iterable[String]): Unit = { contents foreach emit } def emitSettings(contents: Iterable[String]): Unit @@ -32,8 +37,30 @@ trait ProverLike { preambleAssumptions :+= new DebugAxiom(description, terms) terms foreach assume } + + 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 analysisInfos = axiom._2 + 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)) + } + } + + 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 def declare(decl: Decl): Unit def comment(content: String): Unit def saturate(timeout: Int, comment: String): Unit @@ -42,8 +69,9 @@ trait ProverLike { trait Prover extends ProverLike with StatefulComponent { def start(userArgsString: Option[String]): Unit - def assert(goal: Term, timeout: Option[Int] = None): Boolean - def check(timeout: Option[Int] = None): Result + 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 diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 9eb103064..4a763cabc 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -7,47 +7,121 @@ package viper.silicon.interfaces.state 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 trait Chunk { - def substitute(terms: silicon.Map[Term, Term]): Chunk + val perm: Term + val permExp: Option[ast.Exp] + + protected def substitute(terms: silicon.Map[Term, Term]): Chunk } + trait ChunkIdentifer 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 - def permScale(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 + protected def withPerm(newPerm: Term, newPermExp: 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] } +object GeneralChunk { + def applyCondition(chunk: GeneralChunk, newCond: Term, newCondExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { + 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): GeneralChunk = { + val newChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => + chunk.permMinus(finalPerm, newPermExp)}, + 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)}, + 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](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](Set(chunk), {finalPerm => + 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 { 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 - def withPerm(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk - def withSnap(snap: Term, snapExp: 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 + override protected def withPerm(perm: Term, permExp: 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 = { + analysisInfo.decider.registerDerivedChunk[NonQuantifiedChunk](Set(chunk), {_ => + 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 { val quantifiedVars: Seq[Var] val quantifiedVarExps: Option[Seq[ast.LocalVarDecl]] - 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 - def withSnapshotMap(snap: Term): QuantifiedChunk -} \ No newline at end of file + 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 + protected def withSnapshotMap(snap: Term): QuantifiedChunk +} + +object QuantifiedChunk { + def withSnapshotMap(chunk: QuantifiedChunk, snap: Term, analysisInfo: AnalysisInfo): QuantifiedChunk = { + analysisInfo.decider.registerDerivedChunk[QuantifiedChunk](Set(chunk), {_ => + chunk.withSnapshotMap(snap)}, + chunk.perm, analysisInfo, isExhale=false, createLabel=false) + } +} diff --git a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala index 37621ea33..32aba1059 100644 --- a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala +++ b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala @@ -7,12 +7,14 @@ package viper.silicon.resources import viper.silicon.Map +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos 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 { @@ -120,8 +122,9 @@ 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())) { - buildPathCondition(thenDo, info) + 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 { buildPathCondition(otherwise, info) } @@ -147,15 +150,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.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 _)) { - Some(builder(chunk)) + val res = builder(chunk) + 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 3c1cf3240..c0f424137 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -6,18 +6,20 @@ package viper.silicon.rules -import java.util.concurrent._ import viper.silicon.common.concurrency._ import viper.silicon.decider.PathConditionStack +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.reporting.condenseToViperResult 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.dependencyAnalysis.{DependencyType, NoDependencyAnalysisMerge} import viper.silver.reporter.BranchFailureMessage import viper.silver.verifier.Failure +import java.util.concurrent._ import scala.collection.immutable.HashSet trait BranchingRules extends SymbolicExecutionRules { @@ -25,6 +27,7 @@ trait BranchingRules extends SymbolicExecutionRules { condition: Term, conditionExp: (ast.Exp, Option[ast.Exp]), v: Verifier, + analysisInfos: DependencyAnalysisInfos, fromShortCircuitingAnd: Boolean = false) (fTrue: (State, Verifier) => VerificationResult, fFalse: (State, Verifier) => VerificationResult) @@ -36,11 +39,18 @@ object brancher extends BranchingRules { condition: Term, conditionExp: (ast.Exp, Option[ast.Exp]), v: Verifier, + analysisInfos: DependencyAnalysisInfos, fromShortCircuitingAnd: Boolean = false) (fThen: (State, Verifier) => VerificationResult, fElse: (State, Verifier) => VerificationResult) : VerificationResult = { + 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)) + } + 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)) @@ -56,16 +66,18 @@ object brancher extends BranchingRules { && s.quantifiedVariables.map(_._1).exists(condition.freeVariables.contains)) ) + val analysisInfos1 = v.decider.handleAndGetUpdatedAnalysisInfos(analysisInfos, conditionExp._1.info, conditionExp._1) + /* 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(), 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())) + || !v.decider.check(condition, Verifier.config.checkTimeout(), analysisInfos1.withDependencyType(DependencyType.Internal).withMergeInfo(NoDependencyAnalysisMerge()))) val parallelizeElseBranch = s.parallelizeBranches && executeThenBranch && executeElseBranch @@ -97,7 +109,7 @@ object brancher extends BranchingRules { var noOfErrors = 0 val elseBranchVerificationTask: Verifier => VerificationResult = - if (executeElseBranch) { + if (executeElseBranch || Verifier.config.disableInfeasibilityChecks()) { /* [BRANCH-PARALLELISATION] */ /* Compute the following sets * 1. only if the else-branch needs to be explored @@ -143,7 +155,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)) + v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), analysisInfos1) + if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmoke(analysisInfos1.withDependencyType(DependencyType.Internal).withMergeInfo(NoDependencyAnalysisMerge())) var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null var nMacrosOfElseBranchDeciderBefore: Int = 0 @@ -173,7 +186,7 @@ object brancher extends BranchingRules { } val elseBranchFuture: Future[Seq[VerificationResult]] = - if (executeElseBranch) { + if (executeElseBranch || Verifier.config.disableInfeasibilityChecks()) { if (parallelizeElseBranch) { /* [BRANCH-PARALLELISATION] */ v.verificationPoolManager.queueVerificationTask(v0 => { @@ -189,11 +202,12 @@ object brancher extends BranchingRules { } val res = { - val thenRes = if (executeThenBranch) { + val thenRes = if (executeThenBranch || Verifier.config.disableInfeasibilityChecks()) { v.symbExLog.markReachable(uidBranchPoint) executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") - v1.decider.setCurrentBranchCondition(condition, conditionExp) + v1.decider.setCurrentBranchCondition(condition, conditionExp, analysisInfos1) + 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 6988b2d87..2307774c6 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.debugger.DebugExp +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.resources.{NonQuantifiedPropertyInterpreter, Resources} @@ -16,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 @@ -32,7 +34,8 @@ trait ChunkSupportRules extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - description: String) + description: String, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult @@ -46,7 +49,8 @@ trait ChunkSupportRules extends SymbolicExecutionRules { args: Seq[Term], argsExp: Option[Seq[ast.Exp]], ve: VerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult @@ -54,7 +58,8 @@ trait ChunkSupportRules extends SymbolicExecutionRules { (chunks: Iterable[Chunk], id: ChunkIdentifer, args: Iterable[Term], - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) : Option[CH] def findChunksWithID[CH <: NonQuantifiedChunk: ClassTag] @@ -75,11 +80,16 @@ object chunkSupporter extends ChunkSupportRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - description: String) + description: String, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { + if(v.decider.isPathInfeasible){ + 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)((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) @@ -107,14 +117,15 @@ object chunkSupporter extends ChunkSupportRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier) + v: Verifier, + 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)(consumeGreedy(_, _, id, args, _, _, _))((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 { @@ -123,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)((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) 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())) { + if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), analysisInfos)) { Some(ch.snap) } else { Some(Ite(IsPositive(perms), ch.snap.convert(sorts.Snap), Unit)) @@ -139,10 +150,19 @@ object chunkSupporter extends ChunkSupportRules { case _ => None } QS(s2.copy(h = s.h), h2, snap, v1) - case _ if v1.decider.checkSmoke(true) => - Success() // TODO: Mark branch as dead? + 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 _ => - createFailure(ve, v1, s1, "consuming chunk", true) + 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()){ + failure combine QS(s1.copy(h = s.h), s1.h, None, v1) + }else{ + failure + } } } )(Q) @@ -155,7 +175,8 @@ object chunkSupporter extends ChunkSupportRules { args: Seq[Term], perms: Term, permsExp: Option[ast.Exp], - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) : (ConsumptionResult, State, Heap, Option[NonQuantifiedChunk]) = { val consumeExact = terms.utils.consumeExactRead(perms, s.constrainableARPs) @@ -164,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)))) + 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) 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))) { + 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) @@ -179,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 = ch.withPerm(PermMinus(ch.perm, toTake), newPermExp) - val takenChunk = Some(ch.withPerm(toTake, toTakeExp)) + 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())) { + 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), 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())) { + 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))) + 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 = ch.withPerm(PermMinus(ch.perm, perms), newPermExp) - val takenChunk = ch.withPerm(perms, permsExp) + 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)) @@ -203,7 +224,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(), analysisInfos)) { (Complete(), s, h, None) } else { (Incomplete(perms, permsExp), s, h, None) @@ -214,10 +235,10 @@ 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) // 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, analysisInfos) Q(s.copy(functionRecorder = fr1), h1, v) } @@ -227,15 +248,20 @@ object chunkSupporter extends ChunkSupportRules { args: Seq[Term], argsExp: Option[Seq[ast.Exp]], ve: VerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult = { + if(v.decider.isPathInfeasible){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) + return Q(s, h, Unit, v) + } executionFlowController.tryOrFail2[Heap, Term](s.copy(h = h), v)((s1, v1, QS) => { 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, analysisInfos)((s2, tSnap, v2) => QS(s2.copy(h = s.h), s2.h, tSnap, v2)) })(Q) } @@ -246,24 +272,32 @@ object chunkSupporter extends ChunkSupportRules { args: Seq[Term], argsExp: Option[Seq[ast.Exp]], ve: VerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (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, analysisInfos) findRes match { - case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.assertTimeout.getOrElse(0)) => + 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(true) => - if (s.isInPackage) { + 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) } else { 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.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{ + failure + } } } @@ -271,10 +305,11 @@ object chunkSupporter extends ChunkSupportRules { (chunks: Iterable[Chunk], id: ChunkIdentifer, args: Iterable[Term], - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) : Option[CH] = { val relevantChunks = findChunksWithID[CH](chunks, id) - findChunkLiterally(relevantChunks, args) orElse findChunkWithProver(relevantChunks, args, v) + findChunkLiterally(relevantChunks, args) orElse findChunkWithProver(relevantChunks, args, v, analysisInfos) } def findChunksWithID[CH <: NonQuantifiedChunk: ClassTag](chunks: Iterable[Chunk], id: ChunkIdentifer): Iterable[CH] = { @@ -311,9 +346,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, 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())) + 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 ce1306791..e5ccba532 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -7,18 +7,20 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode - -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.dependencyAnalysis._ import viper.silicon.interfaces.VerificationResult import viper.silicon.logger.records.data.{CondExpRecord, ConsumeRecord, ImpliesRecord} import viper.silicon.state._ import viper.silicon.state.terms._ 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._ + +import scala.collection.mutable trait ConsumptionRules extends SymbolicExecutionRules { @@ -29,13 +31,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 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) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult @@ -51,6 +54,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 dependencyType @see [[consume]] * @param Q @see [[consume]] * @return @see [[consume]] */ @@ -58,7 +62,8 @@ trait ConsumptionRules extends SymbolicExecutionRules { as: Seq[ast.Exp], returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult } @@ -72,11 +77,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, analysisInfos: DependencyAnalysisInfos) (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, analysisInfos)((s1, h1, snap, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap, v1)}) @@ -87,7 +92,8 @@ object consumer extends ConsumptionRules { as: Seq[ast.Exp], returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -102,7 +108,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, analysisInfos)((s1, h1, snap1, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap1, v1) @@ -114,7 +120,8 @@ object consumer extends ConsumptionRules { tlcs: Seq[ast.Exp], returnSnap: Boolean, pves: Seq[PartialVerificationError], - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -125,10 +132,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, analysisInfos)(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, 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) @@ -139,14 +146,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, 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)(Q) + consumeTlcs(s, h, tlcs, returnSnap, pves, v, analysisInfos)(Q) } /** Wrapper/decorator for consume that injects the following operations: @@ -158,7 +165,8 @@ object consumer extends ConsumptionRules { a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -172,14 +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 analysisInfos1 = v1.decider.handleAndGetUpdatedAnalysisInfos(analysisInfos, a.info, a) - consumeTlc(s1, h0, a, returnSnap, pve, v1)((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) + 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 = { @@ -190,6 +199,11 @@ object consumer extends ConsumptionRules { * time permissions have been consumed. */ + if(v.decider.isPathInfeasible){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) + 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)) @@ -200,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)(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)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2)((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) }), @@ -220,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)(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)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2)((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)((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) }))) @@ -242,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, 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) - v2.heapSupporter.consumeSingle(s3, h, accPred.loc, tArgs, eArgsNew, loss, lossExp, returnSnap, pve, v2)((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) @@ -282,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, analysisInfos) { case (s1, qvars, qvarExps, Seq(tCond), condNew, Some((Seq(tPerm, tArgs@_*), permArgsNew, tTriggers, (auxGlobals, auxNonGlobals), auxExps)), v1) => v1.heapSupporter.consumeQuantified( s = s1, @@ -310,7 +324,8 @@ object consumer extends ConsumptionRules { negativePermissionReason = NegativePermission(ePerm), notInjectiveReason = QPAssertionNotInjective(resAcc), insufficientPermissionReason = insuffReason, - v1)((s2, h2, snap, v2) => { + v1, + analysisInfos)((s2, h2, snap, v2) => { val s3 = s2.copy(constrainableARPs = s.constrainableARPs, functionRecorder = s2.functionRecorder.leaveQuantifiedExp(qpa)) Q(s3, h2, snap, v2) }) @@ -318,38 +333,38 @@ 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, analysisInfos)((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, analysisInfos)(Q)}) case _: ast.InhaleExhaleExp => createFailure(viper.silicon.utils.consistency.createUnexpectedInhaleExhaleExpressionError(a), v, s, "valid AST") case _ => - evalAndAssert(s, a, returnSnap, pve, v)((s1, t, v1) => { + evalAndAssert(s, a, returnSnap, pve, v, analysisInfos)((s1, t, v1) => { Q(s1, h, t, v1) }) } - consumed } 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, + analysisInfos: DependencyAnalysisInfos) (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)( + 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)((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)((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) }) @@ -366,6 +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, analysisInfos.withSource(StringAnalysisSourceInfo("conditional join", e0.pos))) ), // Assume that entry1.pcs is inverse of entry2.pcs (entry1.data._2, entry2.data._2) match { @@ -386,7 +402,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, analysisInfos: DependencyAnalysisInfos) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -405,22 +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, 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) + 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) { + v2.decider.assert(termToAssert, analysisInfos) { case true => - v2.decider.assume(t, Option.when(withExp)(e), eNew) + 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.handleFailedAssertion(t, analysisInfos, assumeFailedAssertion=false) if (s3.retryLevel == 0 && v2.reportFurtherErrors()){ - v2.decider.assume(t, Option.when(withExp)(e), eNew) + 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 e7fcafedf..3c2a74d08 100644 --- a/src/main/scala/rules/ConsumptionResult.scala +++ b/src/main/scala/rules/ConsumptionResult.scala @@ -6,10 +6,11 @@ package viper.silicon.rules -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 @@ -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, analysisInfos: DependencyAnalysisInfos): 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, analysisInfos)) Complete() else Incomplete(term, exp) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 53713fab3..229fbe10e 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -7,13 +7,10 @@ package viper.silicon.rules import viper.silicon -import viper.silicon.debugger.DebugExp import viper.silicon.Config.JoinMode -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.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces._ import viper.silicon.interfaces.state.ChunkIdentifer import viper.silicon.logger.records.data.{CondExpRecord, EvaluateRecord, ImpliesRecord} @@ -25,9 +22,14 @@ 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, WeightedQuantifier} +import viper.silver.dependencyAnalysis._ 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 @@ -36,11 +38,11 @@ import viper.silver.utility.Common.Rational */ 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, analysisInfos: DependencyAnalysisInfos) (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, analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult @@ -52,7 +54,8 @@ trait EvaluationRules extends SymbolicExecutionRules { optTriggers: Option[Seq[ast.Trigger]], name: String, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + 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 } @@ -61,40 +64,41 @@ 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, analysisInfos: DependencyAnalysisInfos) (Q: (State, List[Term], Option[List[ast.Exp]], Verifier) => VerificationResult) : VerificationResult = - evals2(s, es, Nil, pvef, v)(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) + 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)((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, 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) + 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)) - eval3(s, e, pve, v)((s1, t, eNew, v1) => { + val analysisInfos1 = v.decider.handleAndGetUpdatedAnalysisInfos(analysisInfos, e.info, e) + + 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) + def eval3(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { - /* For debugging only */ e match { case _: ast.TrueLit | _: ast.FalseLit | _: ast.NullLit | _: ast.IntLit | _: ast.FullPerm | _: ast.NoPerm @@ -124,7 +128,7 @@ object evaluator extends EvaluationRules { reserveHeaps = Nil, exhaleExt = false) - eval2(s1, e, pve, v)((s2, t, eNew, v1) => { + eval2(s1, e, pve, v, analysisInfos)((s2, t, eNew, v1) => { val s3 = if (s2.recordPossibleTriggers) e match { @@ -142,7 +146,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, analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { val eOpt = Option.when(withExp)(e) @@ -153,9 +157,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, 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)((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 => @@ -170,8 +174,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, 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 => @@ -182,7 +186,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, 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). @@ -200,10 +204,10 @@ 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, analysisInfos)((s1, tRcvr, eRcvr, v1) => { 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, analysisInfos)((s2, snap, v2) => { val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s2, fa.pos, Some(magicWandSupporter.getEvalHeap(s2))) val newFa = Option.when(withExp)({ if (s1.isEvalInOld) ast.FieldAccess(eRcvr.get, fa.field)(fa.pos, fa.info, fa.errT) @@ -217,15 +221,15 @@ object evaluator extends EvaluationRules { }) case ast.Not(e0) => - eval(s, e0, pve, v)((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)((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)((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) => @@ -235,28 +239,34 @@ 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.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 _ => - evalInOldState(s, heapName, e0, pve, v)((s1, t0, _, v1) => + evalInOldState(s, heapName, e0, pve, v, analysisInfos)((s1, t0, _, v1) => Q(s1, t0, Some(old), v1)) } 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.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 _ => - evalInOldState(s, lbl, e0, pve, v)((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)((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) + 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)((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) @@ -271,30 +281,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, 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)(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)((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)(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)((s1, t0, e0New, v1) => - evalImplies(s1, t0, (e0, e0New), e1, implies.info == FromShortCircuitingAnd, pve, v1)((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) @@ -303,11 +313,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)((s2, v2, QB) => - brancher.branch(s2.copy(parallelizeBranches = false), t0, (e0, e0New), v2)( - (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, 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. */ @@ -329,88 +339,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, 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)((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)((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)((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, 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)((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, 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)((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)((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)((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)((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)((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)((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)((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)((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)((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)((s1, t0, e0New, v1) => - eval(s1, e1, pve, v1)((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)((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, 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)((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, 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)((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)((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)((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)((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)((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) @@ -418,7 +428,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, 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)) @@ -426,8 +436,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, 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)) }) @@ -448,7 +458,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, analysisInfos)((s3, ts, es, v3) => { val possibleConds = v3.heapSupporter.collectForPermConditions(s3, resource, varPairs, ts, es) def evalOptions(s: State, @@ -461,7 +471,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, 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) @@ -528,18 +538,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){ + 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) - v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false) + 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) - 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, auxAnalysisInfos) if (qantOp == Exists) { // For universal quantification, the non-global auxiliary assumptions will contain the information that // forall vars :: all function preconditions are fulfilled. @@ -552,7 +562,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, analysisInfos) } val tQuant = Quantification(qantOp, tVars, tBody, tTriggers, name, quantWeight) @@ -561,16 +571,33 @@ 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(analysisInfos, 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(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 } } case fapp @ ast.FuncApp(funcName, eArgs) => val func = s.program.findFunction(funcName) - evals2(s, eArgs, Nil, _ => pve, v)((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 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) val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s1, fapp.pos) @@ -592,18 +619,8 @@ 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) => { - 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) - }) + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1a, v1, analysisInfos)((s2, v2, QB) => { + /* 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 @@ -620,8 +637,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, analysisInfos.withMergeInfo(SimpleDependencyAnalysisMerge(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 @@ -650,13 +678,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)((s4, snap, v3) => { + 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) 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) + v3.decider.assume(preFApp, preExp, analysisInfos) val funcAnn = func.info.getUniqueInfo[AnnotationInfo] val tFApp = funcAnn match { case Some(a) if a.values.contains("opaque") => @@ -685,9 +714,11 @@ 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, joinExp, v1))((s6, r, v4) - => Q(s6, r._1, r._2, v4)) - }) + })(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))) + Q(s6, r._1, r._2, v4) + })}) case uf@ast.Unfolding( acc @ ast.PredicateAccessPredicate(pa @ ast.PredicateAccess(eArgs, predicateName), ePerm), @@ -696,8 +727,8 @@ 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) => { + evals(s, eArgs, _ => pve, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm.getOrElse(ast.FullPerm()()), pve, v1, analysisInfos)((s2, tPerm, ePermNew, v2) => { val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s2, uf.pos) val unfoldingNew = eArgsNew.map(args => uf.copy(acc = acc.copy(loc = pa.copy(args = args)(pa.pos, pa.info, pa.errT), @@ -707,9 +738,9 @@ object evaluator extends EvaluationRules { else ast.DebugLabelledOld(unfoldingNew.get, debugLabel)(uf.pos, uf.info, uf.errT) }) val s2a = if (Verifier.config.enableDebugging()) s2.copy(oldHeaps = s2.oldHeaps + (debugHeapName -> s2.h)) else s2 - v2.decider.assert(IsPositive(tPerm)) { // TODO: Replace with permissionSupporter.assertNotNegative + v2.decider.assert(IsPositive(tPerm), analysisInfos) { // TODO: Replace with permissionSupporter.assertNotNegative case true => - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s2a, v2)((s3, v3, QB) => { + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s2a, 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 @@ -721,7 +752,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, analysisInfos)((s5, snap, v4) => { val fr6 = s5.functionRecorder.recordSnapshot(pa, v4.decider.pcs.branchConditions, snap.get) .changeDepthBy(+1) @@ -736,7 +767,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) + 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) @@ -746,7 +777,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, analysisInfos.withDependencyType(DependencyType.Internal), true)((s8, v5) => { val s9 = s8.copy(g = s7.g, functionRecorder = s8.functionRecorder.changeDepthBy(-1), recordVisited = s3.recordVisited, @@ -755,10 +786,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, analysisInfos)((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, analysisInfos)((s8, v5) => { val s9 = s8.copy(g = s7.g, functionRecorder = s8.functionRecorder.changeDepthBy(-1), recordVisited = s3.recordVisited, @@ -767,17 +798,21 @@ 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, analysisInfos)((s9, t9, e9, v9) => QB(s9, (t9, e9), v9))}) } }) })(join(eIn.typ, "joined_unfolding", s2a.relevantQuantifiedVariables.map(_._1), - joinExp, v2))((s12, r12, v7) + joinExp, 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})") - createFailure(pve dueTo NonPositivePermission(ePerm.get), v2, s2a, 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, 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()) + 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 + }})) } 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]) @@ -791,117 +826,123 @@ object evaluator extends EvaluationRules { else ast.DebugLabelledOld(apl, debugLabel)(apl.pos, apl.info, apl.errT) }) val sa = if (Verifier.config.enableDebugging()) s.copy(oldHeaps = s.oldHeaps + (debugHeapName -> s.h)) else s - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](sa, v)((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)) + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](sa, 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), - joinExp, v))((s4, r4, v4) + joinExp, v, analysisInfos))((s4, r4, v4) => 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, analysisInfos )((s2, _, v2) => { val s3 = s2.copy(g = s.g, h = s.h) - eval(s3, eIn, pve, v2)(Q) + eval(s3, eIn, pve, v2, analysisInfos)(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, 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)({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))) { + v1.decider.assert(AtLeast(t1, IntLiteral(0)), analysisInfos) { case true => - v1.decider.assert(Less(t1, SeqLength(t0))) { + 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.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)())()) - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New) + 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.handleFailedAssertion(AtLeast(t1, IntLiteral(0)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp1, assertExp1New) + 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))) { + 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.handleFailedAssertion(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) if (v1.reportFurtherErrors()) { - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New) + 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)((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)((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)((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)((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)((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)({ 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))) { + 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))) { + 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.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))())()) - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp3, assertExp3New) + 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.handleFailedAssertion(AtLeast(t1, IntLiteral(0)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp, assertExpNew) + 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))) { + 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.handleFailedAssertion(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) if (v1.reportFurtherErrors()) { - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New) + 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)((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))) @@ -910,7 +951,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, analysisInfos) Q(s1, tSeq, esNew.map(en => ast.ExplicitSeq(en)(e.pos, e.info, e.errT)), v1)}) /* Sets and multisets */ @@ -921,68 +962,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, 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)((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)((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)((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)((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)((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)((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)((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)((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)((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)((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)((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)((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)((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)) @@ -993,28 +1034,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, analysisInfos)((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, 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)((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)((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)({ + 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))) { + 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.handleFailedAssertion(SetIn(keyT, MapDomain(baseT)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew) + v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, analysisInfos.withDependencyType(DependencyType.Explicit)) failure1 combine Q(s1, MapLookup(baseT, keyT), eNew, v1) } else { failure1 @@ -1023,13 +1065,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, 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)({ + 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) }) @@ -1059,7 +1101,8 @@ object evaluator extends EvaluationRules { optTriggers: Option[Seq[ast.Trigger]], name: String, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Seq[Var], /* Variables from vars */ Option[Seq[ast.LocalVarDecl]], @@ -1087,18 +1130,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, 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) + 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)((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, 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 = @@ -1128,13 +1171,14 @@ object evaluator extends EvaluationRules { eRhs: ast.Exp, fromShortCircuitingAnd: Boolean, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : 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)( - (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, analysisInfos)((s1, v1, QB) => + 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 => { assert(entries.length <= 2) @@ -1152,7 +1196,8 @@ object evaluator extends EvaluationRules { label: String, e: ast.Exp, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { @@ -1161,7 +1206,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, 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. @@ -1191,10 +1236,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, analysisInfos: DependencyAnalysisInfos) (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, analysisInfos)((s1, tArgs, eArgsNew, v1) => Q(s1, ChunkIdentifier(resacc.res(s1.program), s1.program), tArgs, eArgsNew, v1)) } @@ -1203,11 +1248,12 @@ object evaluator extends EvaluationRules { e1: ast.Exp, termOp: ((Term, Term)) => T, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + 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)(Q) + evalBinOp(s, e0, e1, (t0, t1) => termOp((t0, t1)), pve, v, analysisInfos)(Q) } private def evalBinOp[T <: Term] @@ -1216,12 +1262,13 @@ object evaluator extends EvaluationRules { e1: ast.Exp, termOp: (Term, Term) => T, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (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, analysisInfos)((s1, t0, e0New, v1) => + eval(s1, e1, pve, v1, analysisInfos)((s2, t1, e1New, v2) => Q(s2, termOp(t0, t1), e0New, e1New, v2))) } @@ -1232,19 +1279,21 @@ object evaluator extends EvaluationRules { tDivisor: Term, tZero: Term, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { - v.decider.assert(tDivisor !== tZero){ + 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.handleFailedAssertion(tDivisor !== tZero, analysisInfos, assumeFailedAssertion=false) if (s.retryLevel == 0 && v.reportFurtherErrors()) { - v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew) + v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, analysisInfos.withDependencyType(DependencyType.Explicit)) failure combine Q(s, t, v) } else failure } @@ -1253,11 +1302,12 @@ object evaluator extends EvaluationRules { def evalTriggers(s: State, silverTriggers: Seq[ast.Trigger], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (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, 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, @@ -1285,7 +1335,8 @@ object evaluator extends EvaluationRules { eTriggerSets: TriggerSets[ast.Exp], tTriggersSets: TriggerSets[Term], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, TriggerSets[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1293,15 +1344,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, analysisInfos)((s1, ts, v1) => + evalTriggers(s1, eTriggerSets.tail, tTriggersSets :+ ts, pve, v1, analysisInfos)(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, 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) + private def evalTrigger(s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Seq[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1374,7 +1425,7 @@ object evaluator extends EvaluationRules { */ val r = - evals(s.copy(triggerExp = true), remainingTriggerExpressions, _ => pve, v)((s1, remainingTriggerTerms, _, v1) => { + evals(s.copy(triggerExp = true), remainingTriggerExpressions, _ => pve, v, analysisInfos)((s1, remainingTriggerTerms, _, v1) => { optRemainingTriggerTerms = Some(remainingTriggerTerms) pcDelta = v1.decider.pcs.after(preMark).assumptions //decider.π -- πPre pcDeltaExp = v1.decider.pcs.after(preMark).assumptionExps @@ -1390,7 +1441,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) + // TODO ake: wrap pcDelta with labels? + v.decider.assume(pcDelta, Option.when(withExp)(DebugExp.createInstance("pcDeltaExp", children = pcDeltaExp)), enforceAssumption = false, analysisInfos.withDependencyType(DependencyType.Internal)) Q(s.copy(functionRecorder = functionRecorder), cachedTriggerTerms ++ remainingTriggerTerms, v) case _ => for (e <- remainingTriggerExpressions) @@ -1404,14 +1456,22 @@ object evaluator extends EvaluationRules { joinFunctionName: String, joinFunctionArgs: Seq[Term], joinedExp: Option[ast.Exp], - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (entries: Seq[JoinDataEntry[(Term, Option[ast.Exp])]]) : (State, (Term, Option[ast.Exp])) = { 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. @@ -1431,13 +1491,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)} + joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, analysisInfos)} (sJoined, (joinTerm, joinedExp)) } } - def evalHeapTrigger(s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier) + 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() @@ -1445,18 +1505,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, analysisInfos) 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, 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) + 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) @@ -1467,7 +1527,8 @@ object evaluator extends EvaluationRules { private def generateResourceTrigger(ra: ast.ResourceAccess, s: State, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) : (Seq[Term], Seq[Term], Term, Seq[SnapshotMapDefinition]) = { var axioms = Seq.empty[Term] var triggers = Seq.empty[Term] @@ -1488,7 +1549,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, analysisInfos)((_, tArgs, _, _) => { axioms = axioms ++ smDef1.valueDefinitions mostRecentTrig = ResourceTriggerFunction(resource, smDef1.sm, tArgs, s.program) triggers = triggers :+ mostRecentTrig @@ -1506,7 +1567,8 @@ object evaluator extends EvaluationRules { s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { assert( @@ -1517,16 +1579,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, 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)((s2, v2, QB) => - brancher.branch(s2.copy(parallelizeBranches = false), if (constructor == Or) t0 else Not(t0), expPair, v2, 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)((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 763ac1afa..7673ef2bd 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -6,26 +6,30 @@ package viper.silicon.rules -import viper.silicon.debugger.DebugExp -import viper.silicon.common.collections.immutable.InsertionOrderedSet 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.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions +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} import viper.silicon.state._ 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.cfg.silver.SilverCfg +import viper.silver.cfg.silver.SilverCfg.{SilverBlock, SilverEdge} import viper.silver.cfg.{ConditionalEdge, StatementBlock} +import viper.silver.dependencyAnalysis._ +import viper.silver.verifier.errors._ +import viper.silver.verifier.reasons._ +import viper.silver.verifier.{CounterexampleTransformer, NullPartialVerificationError, PartialVerificationError} +import viper.silver.{ast, cfg} + +import scala.annotation.unused trait ExecutionRules extends SymbolicExecutionRules { def exec(s: State, @@ -64,11 +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) - eval(s1, ce.condition, IfFailed(ce.condition), v)((s2, tCond, condNew, v1) => + 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. */ - brancher.branch(s2.copy(parallelizeBranches = false), tCond, (ce.condition, condNew), v1)( + 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) @@ -88,8 +93,9 @@ object executor extends ExecutionRules { def handleOutEdge(s: State, edge: SilverEdge, v: Verifier): State = { edge.kind match { - case cfg.Kind.Out => - val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, s.invariantContexts.head, v) + case cfg.Kind.Out if !v.decider.isPathInfeasible => + 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) s1 @@ -118,7 +124,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]] && @@ -126,8 +132,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]] @@ -140,10 +148,12 @@ object executor extends ExecutionRules { case _ => false }) - eval(s, cedge1.condition, pvef(cedge1.condition), v)((s1, t0, condNew, v1) => + 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. - joiner.join[scala.Null, scala.Null](s1, v1, resetState = false)((s2, v2, QB) => { - brancher.branch(s2, t0, (cedge1.condition, condNew), v2)( + 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)) @@ -152,6 +162,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 _ => @@ -172,8 +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 res = eval(s, thenEdge.condition, IfFailed(thenEdge.condition), v)((s2, tCond, eCondNew, v1) => - brancher.branch(s2, tCond, (thenEdge.condition, eCondNew), v1)( + 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) => { follow(s3, thenEdge, v3, joinPoint)(Q) }, @@ -246,6 +259,9 @@ object executor extends ExecutionRules { map.updated(x, xNew)})) val sBody = s.copy(g = gBody, h = v.heapSupporter.getEmptyHeap(s.program)) + 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) val sortedEdges = otherEdges ++ outEdges @@ -258,7 +274,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)((s1, v1) => { + produces(s0, freshSnap, invs, ContractNotWellformed, v0, analysisInfosInv)((s1, v1) => { phase1data = phase1data :+ (s1, v1.decider.pcs.after(mark), v1.decider.freshFunctions /* [BRANCH-PARALLELISATION] */, @@ -267,7 +283,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, 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 @@ -276,9 +292,10 @@ 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.assume(pcs.assumptions, Option.when(withExp)(DebugExp.createInstance("Loop invariant", pcs.assumptionExps)), false) + 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 (v2.decider.checkSmoke()) + if (!Verifier.config.disableInfeasibilityChecks() && v2.decider.checkSmoke(analysisInfosLoopInternal)) Success() else { execs(s3, stmts, v2)((s4, v3) => { @@ -288,7 +305,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, DependencyAnalysisInfos.DefaultDependencyAnalysisInfos)((_, _, _, _) => Success()) }) } @@ -302,7 +319,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)((_, _, _) => + val analysisInfosInv = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos + consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, analysisInfosInv)((_, _, _) => Success()) } } @@ -322,12 +340,14 @@ object executor extends ExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) - exec2(s, stmt, v)((s1, v1) => { + val analysisInfos = v.decider.handleAndGetUpdatedAnalysisInfos(DefaultDependencyAnalysisInfos, stmt.info, stmt) + exec2(s, stmt, v, analysisInfos)((s1, v1) => { v1.symbExLog.closeScope(sepIdentifier) - Q(s1, v1)}) + Q(s1, v1) + }) } - def exec2(state: State, stmt: ast.Stmt, v: Verifier) + def exec2(state: State, stmt: ast.Stmt, v: Verifier, analysisInfos: DependencyAnalysisInfos) (continuation: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -361,8 +381,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) + 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 @@ -374,10 +394,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) - v2.heapSupporter.execFieldAssign(s2, ass, tRcvr, eRcvrNew, tSnap, eRhsNew, pve, v2)(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) }) }) @@ -386,10 +406,10 @@ 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()())()) - v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst) + 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) @@ -399,7 +419,7 @@ object executor extends ExecutionRules { } else { val fld = flds.head val snap = v.decider.fresh(fld.name, v.symbolConverter.toSort(fld.typ), Option.when(withExp)(extractPTypeFromExp(x))) - v.heapSupporter.produceSingle(s, fld, Seq(tRcvr), eRcvr, snap, None, p, pExp, NullPartialVerificationError, false, v)((s1, v1) => { + v.heapSupporter.produceSingle(s, fld, Seq(tRcvr), eRcvr, snap, None, p, pExp, NullPartialVerificationError, false, v, analysisInfos)((s1, v1) => { addFieldPerms(s1, flds.tail, v1)(QB) }) } @@ -410,37 +430,50 @@ object executor extends ExecutionRules { val s1 = s0.copy(g = s0.g + (x, (tRcvr, eRcvrNew))) val (debugHeapName, _) = v.getDebugOldLabel(s1, stmt.pos, Some(magicWandSupporter.getEvalHeap(s1))) 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, analysisInfos) Q(s2, v0) }) case inhale @ ast.Inhale(a) => a match { - case _: ast.FalseLit => + 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)((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.checkSmoke(analysisInfos) Q(s1, v1)}) } case exhale @ ast.Exhale(a) => val pve = ExhaleFailed(exhale) - consume(s, a, false, pve, v)((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 => /* "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(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.handleFailedAssertion(False, analysisInfos, v1.reportFurtherErrors()) + if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine QS(s1, v1) else failure + } + })((s2, v2) => + if (Verifier.config.disableInfeasibilityChecks()) + Q(s2, v2) else - createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) - })((_, _) => Success()) + Success() + ) case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => - val r = - consume(s, a, false, AssertFailed(assert), v)((_, _, _) => + 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) @@ -448,6 +481,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) @@ -456,11 +491,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, analysisInfos1)((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, analysisInfos1)((s1, _, v1) => { val s2 = s1.copy(h = s.h, reserveHeaps = s.reserveHeaps) Q(s2, v1)}) @@ -469,6 +504,7 @@ object executor extends ExecutionRules { case ast.MethodCall(methodName, _, _) if !Verifier.config.disableHavocHack407() && methodName.startsWith(hack407_method_name_prefix) => + 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 @@ -476,11 +512,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 => - bc.withSnap(freshSnap(bc.snap.sort, v), None) + 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) @@ -499,11 +535,14 @@ object executor extends ExecutionRules { val pveCall = CallFailed(call) val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) + 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)((s1, tArgs, eArgsNew, v1) => { + val eArgsWithDAInfo = DependencyAnalysisMergeInfo.attachExpMergeInfo(eArgs, None) + evals(s, eArgsWithDAInfo, _ => pveCall, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => { v1.symbExLog.closeScope(paramId) val exampleTrafo = CounterexampleTransformer({ case ce: SiliconCounterexample => ce.withStore(s1.g) @@ -512,20 +551,39 @@ 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, analysisInfos.withMergeInfo(SimpleDependencyAnalysisMerge(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)((s3, _, v2) => { + + + 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) 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)((s5, v3) => { + + 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) val gLhs = Store(lhs.zip(outs) @@ -533,6 +591,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)})})}) @@ -540,12 +599,13 @@ object executor extends ExecutionRules { assert(s.constrainableARPs.isEmpty) v.decider.startDebugSubExp() val ePerm = pap.perm + val predicate = s.program.findPredicate(predicateName) 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, 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)((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) } @@ -557,13 +617,13 @@ object executor extends ExecutionRules { val ePerm = pap.perm val predicate = s.program.findPredicate(predicateName) 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, 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)((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)( + 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) @@ -573,7 +633,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, analysisInfos)((s1, chWand, v1) => { val hOps = s1.reserveHeaps.head + chWand assert(s.exhaleExt || s1.reserveHeaps.length == 1) @@ -599,7 +659,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, analysisInfos) case _ => s2 } @@ -608,13 +668,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, analysisInfos)(Q) case havoc: ast.Quasihavoc => - havocSupporter.execHavoc(havoc, v, s)(Q) + havocSupporter.execHavoc(havoc, v, s, analysisInfos)(Q) case havocall: ast.Quasihavocall => - havocSupporter.execHavocall(havocall, v, s)(Q) + havocSupporter.execHavocall(havocall, v, s, analysisInfos)(Q) case viper.silicon.extensions.TryBlock(body) => var bodySucceeded = false @@ -638,9 +698,9 @@ 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, analysisInfos: DependencyAnalysisInfos): (Term, Option[ast.Exp]) = { rhs match { - case _: Var | _: Literal => + case _: Var | _: Literal if !v.decider.isDependencyAnalysisEnabled => (rhs, rhsExpNew) case _ => @@ -665,7 +725,7 @@ object executor extends ExecutionRules { } else { (None, None) } - v.decider.assumeDefinition(BuiltinEquals(t, rhs), debugExp) + 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 8b3a1cc88..0cbcc164e 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -7,6 +7,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.VerificationResult import viper.silicon.rules.evaluator.{eval, evalQuantified, evals} import viper.silicon.state._ @@ -33,7 +34,8 @@ object havocSupporter extends SymbolicExecutionRules { */ def execHavoc(havoc: ast.Quasihavoc, v: Verifier, - s: State) + s: State, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -42,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, 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) + val newHeap = v1.heapSupporter.havocResource(s1, lhsTerm, resource, condInfo, v1, analysisInfos) Q(s1.copy(h = newHeap), v1) }) @@ -70,7 +72,8 @@ object havocSupporter extends SymbolicExecutionRules { */ def execHavocall(havocall: ast.Quasihavocall, v: Verifier, - s: State) + s: State, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -95,7 +98,8 @@ object havocSupporter extends SymbolicExecutionRules { optTriggers = None, // Triggers: none needed for Havocall name = qid, pve = pve, - v = v) + v = v, + analysisInfos = analysisInfos) { case (s1, tVars, eVars, Seq(tCond), _, Some((tArgs, eArgs, Seq(), _, _)), v1) => // Seq() represents an empty list of Triggers @@ -117,11 +121,14 @@ 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)) - v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp) - v.decider.assert(receiverInjectivityCheck) { - case false => createFailure(pve dueTo notInjectiveReason, v, s1, receiverInjectivityCheck, "QP receiver injective") + 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, 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.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( @@ -141,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) + 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) + 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 649c1c629..dd6573f53 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -9,12 +9,13 @@ package viper.silicon.rules import viper.silicon import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis._ 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 -import viper.silicon.state.{BasicChunk, BasicChunkIdentifier, ChunkIdentifier, Heap, MagicWandChunk, MagicWandIdentifier, QuantifiedBasicChunk, QuantifiedFieldChunk, QuantifiedMagicWandChunk, QuantifiedPredicateChunk, State, Store} +import viper.silicon.state._ import viper.silicon.state.terms._ import viper.silicon.state.terms.perms.IsPositive import viper.silicon.state.terms.predef.{`?r`, `?s`} @@ -23,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, SimpleDependencyAnalysisMerge} import viper.silver.parser.PUnknown import viper.silver.verifier.reasons.{InsufficientPermission, MagicWandChunkNotFound} import viper.silver.verifier.{ErrorReason, PartialVerificationError, VerificationError} @@ -35,7 +37,8 @@ trait HeapSupportRules extends SymbolicExecutionRules { tRcvr: Term, eRcvr: Option[ast.Exp], ve: VerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult @@ -45,7 +48,8 @@ trait HeapSupportRules extends SymbolicExecutionRules { identifier: ChunkIdentifer, tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Verifier) => VerificationResult): VerificationResult def isPossibleTrigger(s: State, fa: ast.FieldAccess): Boolean @@ -57,7 +61,8 @@ trait HeapSupportRules extends SymbolicExecutionRules { tRhs: Term, eRhsNew: Option[ast.Exp], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -65,7 +70,8 @@ trait HeapSupportRules extends SymbolicExecutionRules { resAcc: ast.ResourceAccess, tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], - v: Verifier): State + v: Verifier, + analysisInfos: DependencyAnalysisInfos): State def consumeSingle(s: State, h: Heap, @@ -76,7 +82,8 @@ trait HeapSupportRules extends SymbolicExecutionRules { ePerm: Option[ast.Exp], returnSnap: Boolean, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult def consumeQuantified(s: State, @@ -104,7 +111,8 @@ trait HeapSupportRules extends SymbolicExecutionRules { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult def produceSingle(s: State, @@ -117,7 +125,8 @@ trait HeapSupportRules extends SymbolicExecutionRules { ePerm: Option[ast.Exp], pve: PartialVerificationError, mergeAndTrigger: Boolean, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult): VerificationResult def produceQuantified(s: State, @@ -144,14 +153,16 @@ trait HeapSupportRules extends SymbolicExecutionRules { pve: PartialVerificationError, negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult): VerificationResult def havocResource(s: State, lhs: Term, resource: ast.Resource, condInfo: HavocHelperData, - v: Verifier): Heap + v: Verifier, + analysisInfos: DependencyAnalysisInfos): Heap def collectForPermConditions(s: State, resource: ast.Resource, @@ -175,18 +186,26 @@ class DefaultHeapSupportRules extends HeapSupportRules { tRhs: Term, eRhsNew: Option[ast.Exp], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { + if(v.decider.isPathInfeasible){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) + v.decider.dependencyAnalyzer.addAssumption(False, analysisInfos) + return Q(s, v) + } + val field = ass.lhs.field val ve = pve dueTo InsufficientPermission(ass.lhs) if (s.qpFields.contains(field)) { 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, analysisInfos) + val s2 = triggerResourceIfNeeded(s, ass.lhs, Seq(tRcvr), eRcvrNew.map(Seq(_)), v, analysisInfos) v.decider.clearModel() + 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, @@ -199,7 +218,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { FullPerm, Option.when(withExp)(ast.FullPerm()()), chunkOrderHeuristics, - v + v, + lhsSourceInfos ) result match { case (Complete(), s3, remainingChunks) => @@ -207,25 +227,28 @@ 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) + 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) + 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) + val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString}.${field.name})")) + 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, Some(magicWandSupporter.getEvalHeap(s4))) 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.handleFailedAssertion(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" - 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 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(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(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, Some(magicWandSupporter.getEvalHeap(s5))) @@ -242,9 +265,15 @@ class DefaultHeapSupportRules extends HeapSupportRules { identifier: ChunkIdentifer, tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Verifier) => VerificationResult): VerificationResult = { + if(v.decider.isPathInfeasible){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) + 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. @@ -270,7 +299,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { case w: ast.MagicWand => MagicWandIdentifier(w, s2.program).toString } DebugExp.createInstance(s"Resource trigger(${name}($argsString))", isInternal_ = true) - })) + }), analysisInfos.withDependencyType(DependencyType.Trigger)) } val currentPermAmount = ResourcePermissionLookup(res, pmDef.pm, tArgs, s2.program) @@ -280,7 +309,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(_)), analysisInfos.withDependencyType(DependencyType.Internal)) val s3 = if (Verifier.config.enableDebugging()) s2.copy(oldHeaps = s2.oldHeaps + (debugHeapName -> h)) else s2 s3 case _ => s2 @@ -304,9 +333,18 @@ class DefaultHeapSupportRules extends HeapSupportRules { tRcvr: Term, eRcvr: Option[ast.Exp], ve: VerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { + if(v.decider.isPathInfeasible){ + 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 + return Q(s, newVar, v) + } + if (s.qpFields.contains(fa.field)) { val (relevantChunks, _) = quantifiedChunkSupporter.splitHeap[QuantifiedFieldChunk](s.h, BasicChunkIdentifier(fa.field.name)) @@ -320,11 +358,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)), 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) + v.decider.assume(trigger, triggerExp, analysisInfos.withDependencyType(DependencyType.Trigger)) } if (s.triggerExp) { val fvfLookup = Lookup(fa.field.name, fvfDef.sm, tRcvr) @@ -333,9 +371,12 @@ class DefaultHeapSupportRules extends HeapSupportRules { Q(s2, fvfLookup, v) } else { val toAssert = IsPositive(totalPermissions.replace(`?r`, tRcvr)) - v.decider.assert(toAssert) { + v.decider.assert(toAssert, analysisInfos) { 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.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 => val fvfLookup = Lookup(fa.field.name, fvfDef.sm, tRcvr) val fr1 = s.functionRecorder.recordSnapshot(fa, v.decider.pcs.branchConditions, fvfLookup).recordFvfAndDomain(fvfDef) @@ -368,7 +409,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, analysisInfos.withDependencyType(DependencyType.Trigger)) } val (permCheck, permCheckExp, s3) = if (s2.triggerExp) { @@ -384,9 +425,12 @@ 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,analysisInfos) { case false => - createFailure(ve, v, s3, permCheck, permCheckExp) + val failure = createFailure(ve, v, s3, permCheck, permCheckExp) + 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 => val smLookup = Lookup(fa.field.name, sm, tRcvr) val fr2 = @@ -397,7 +441,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, 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) @@ -409,7 +453,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { resAcc: ast.ResourceAccess, tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], - v: Verifier): State = { + v: Verifier, + analysisInfos: DependencyAnalysisInfos): State = { if (s.isUsedAsTrigger(resAcc.res(s.program))) { val resource = resAcc.res(s.program) val chunkId = ChunkIdentifier(resource, s.program) @@ -431,7 +476,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, analysisInfos.withDependencyType(DependencyType.Trigger)) s.copy(smCache = smCache1, functionRecorder = s.functionRecorder.recordFvfAndDomain(smDef1)) } else { s @@ -448,7 +493,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { ePerm: Option[ast.Exp], pve: PartialVerificationError, mergeAndTrigger: Boolean, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val useQPs = s.isQuantifiedResource(resource) if (useQPs) { @@ -456,17 +502,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, analysisInfos)(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, 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(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(analysisInfos)) 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 +520,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, analysisInfos.withDependencyType(DependencyType.Trigger)) } Q(s2.copy(h = h2), v2) }) @@ -494,22 +540,28 @@ class DefaultHeapSupportRules extends HeapSupportRules { ePerm: Option[ast.Exp], returnSnap: Boolean, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { + if(v.decider.isPathInfeasible){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) + return Q(s, h, Some(Unit), v) + } + val resource = resAcc.res(s.program) val useQPs = s.isQuantifiedResource(resource) if (useQPs) { 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, 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)(Q) + chunkSupporter.consume(s, h, resource, tArgs, eArgs, tPerm, ePerm, returnSnap, ve, v, description, analysisInfos)(Q) } } @@ -537,7 +589,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { pve: PartialVerificationError, negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult): VerificationResult = { val tSnap = resource match { case f: ast.Field => @@ -573,7 +626,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { pve, negativePermissionReason, notInjectiveReason, - v + v, + analysisInfos )(Q) } @@ -602,8 +656,14 @@ class DefaultHeapSupportRules extends HeapSupportRules { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { + if(v.decider.isPathInfeasible){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) + return Q(s, h, Some(Unit), v) + } + quantifiedChunkSupporter.consume( s, h, @@ -630,7 +690,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { negativePermissionReason, notInjectiveReason, insufficientPermissionReason, - v + v, + analysisInfos )(Q) } @@ -638,11 +699,12 @@ class DefaultHeapSupportRules extends HeapSupportRules { lhs: Term, resource: ast.Resource, condInfo: HavocHelperData, - v: Verifier): Heap = { + v: Verifier, + analysisInfos: DependencyAnalysisInfos): Heap = { if (s.isQuantifiedResource(resource)) { - havocQuantifiedResource(s, lhs, resource, condInfo, v) + havocQuantifiedResource(s, lhs, resource, condInfo, v, analysisInfos) } else { - havocNonQuantifiedResource(s, lhs, resource, condInfo, v) + havocNonQuantifiedResource(s, lhs, resource, condInfo, v, analysisInfos) } } @@ -663,7 +725,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { lhs: Term, resource: ast.Resource, condInfo: HavocHelperData, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) : Heap = { val id = ChunkIdentifier(resource, s.program) @@ -674,12 +737,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(analysisInfos)) 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(analysisInfos)) } Heap(otherChunks ++ newChunks) } @@ -705,7 +768,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { lhs: Term, resource: ast.Resource, condInfo: HavocHelperData, - v: Verifier) : Heap = { + v: Verifier, + 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). @@ -758,9 +822,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, analysisInfos) - ch.withSnapshotMap(newSm) + 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 e61691995..d215f540d 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -6,9 +6,10 @@ package viper.silicon.rules -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.dependencyAnalysis.{AnalysisInfo, DependencyAnalysisInfos} import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.logger.records.structural.JoiningRecord import viper.silicon.state.State @@ -16,26 +17,29 @@ 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 scala.annotation.unused +import viper.silver.ast.NoPosition +import viper.silver.dependencyAnalysis.{DependencyType, StringAnalysisSourceInfo} case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathConditions) { + + 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) + 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], @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.decider, v.decider.dependencyAnalyzer, analysisInfos)) } } trait JoiningRules extends SymbolicExecutionRules { - def join[D, JD](s: State, v: Verifier, 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) @@ -43,7 +47,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, analysisInfos: DependencyAnalysisInfos, resetState: Boolean = true) (block: (State, Verifier, (State, D, Verifier) => VerificationResult) => VerificationResult) (merge: Seq[JoinDataEntry[D]] => (State, JD)) (Q: (State, JD, Verifier) => VerificationResult) @@ -102,13 +106,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, 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))) + 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 8707efd9e..e04af3c73 100644 --- a/src/main/scala/rules/LetSupporter.scala +++ b/src/main/scala/rules/LetSupporter.scala @@ -6,21 +6,24 @@ package viper.silicon.rules -import viper.silver.ast -import viper.silver.verifier.PartialVerificationError +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos 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) + (s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, + analysisInfos: DependencyAnalysisInfos) (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, + analysisInfos: DependencyAnalysisInfos) (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, + analysisInfos: DependencyAnalysisInfos) (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, analysisInfos)(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, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult = { - handle(s, Nil, let, pve, v)(Q) + handle(s, Nil, let, pve, v, analysisInfos)(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, + analysisInfos: DependencyAnalysisInfos) (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, 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)(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 d8d99e2d5..51297a127 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -6,10 +6,11 @@ package viper.silicon.rules -import viper.silicon.debugger.DebugExp import viper.silicon._ import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces._ import viper.silicon.interfaces.state._ import viper.silicon.state._ @@ -17,9 +18,10 @@ 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.dependencyAnalysis.{DependencyType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} import viper.silver.parser.PUnknown import viper.silver.verifier.PartialVerificationError @@ -89,13 +91,15 @@ object magicWandSupporter extends SymbolicExecutionRules { wand: ast.MagicWand, snap: MagicWandSnapshot, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (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, 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(analysisInfos)) + Q(s1, newChunk, v1) + }) } /** @@ -111,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, 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", true))) + ), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot definition", isInternal_ = true)), analysisInfos) magicWandSnapshot } @@ -132,13 +136,14 @@ object magicWandSupporter extends SymbolicExecutionRules { def evaluateWandArguments(s: State, wand: ast.MagicWand, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + 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)((s2, ts, esNew, v1) => { + evals(s1, es, _ => pve, v, analysisInfos)((s2, ts, esNew, v1) => { Q(s2.copy(exhaleExt = s.exhaleExt), ts, esNew, v1) }) } @@ -150,12 +155,13 @@ object magicWandSupporter extends SymbolicExecutionRules { pLossExp: Option[ast.Exp], failure: Failure, qvars: Seq[Var], - v: Verifier) + v: Verifier, + 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()) + 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 @@ -181,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))) + 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. @@ -196,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()) { + if (v.decider.checkSmoke(analysisInfos)) { (Complete(), sOut, h +: hps, cch +: cchs) } else { (success, sOut, h +: hps, cch +: cchs) @@ -207,7 +213,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.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) + if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, heaps.reverse, consumedChunks.reverse, v) else failure } } @@ -234,10 +242,12 @@ object magicWandSupporter extends SymbolicExecutionRules { wand: ast.MagicWand, proofScript: ast.Seqn, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Chunk, Verifier) => VerificationResult) : VerificationResult = { + val s = if (state.exhaleExt) state else state.copy(reserveHeaps = v.heapSupporter.getEmptyHeap(state.program) :: state.h :: Nil) @@ -304,16 +314,17 @@ object magicWandSupporter extends SymbolicExecutionRules { def createWandChunkAndRecordResults(s: State, freshSnapRoot: Var, snapRhs: Term, - v: Verifier) + v: Verifier, + 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) + val wandSnapshot = this.createMagicWandSnapshot(freshSnapRoot, snapRhs, v, analysisInfos) val bodyVars = wand.subexpressionsToEvaluate(s.program) - evals(s, bodyVars, _ => pve, v)((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. @@ -324,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) + v2.decider.assumeDefinition(smValueDef, debugExp, analysisInfos) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, formalVarExps, wand, tArgs, - eArgsNew, FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program) + 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(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(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)) @@ -353,7 +364,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 @@ -363,7 +376,8 @@ 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) => { + 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) @@ -399,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 + wand.right, true, pve, proofScriptVerifier, analysisInfos )((s3, snapRhs, v3) => { - - createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3) + analysisLabels = v.decider.pcs.after(prePackageMark).analysisLabels + createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, analysisInfos) }) }) }) @@ -414,9 +428,12 @@ 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) + 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, analysisInfos.withDependencyType(DependencyType.Internal))) + recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { prevRes && { val (state, branchConditions, branchConditionsExp, conservedPcs, magicWandChunk) = recordedState @@ -430,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), (exp, expNew)) + 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, conservedPcs._2) + 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) @@ -455,13 +472,14 @@ object magicWandSupporter extends SymbolicExecutionRules { def applyWand(s: State, wand: ast.MagicWand, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (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, analysisInfos)((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, 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: @@ -478,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", true))) + 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)((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) @@ -503,7 +521,8 @@ object magicWandSupporter extends SymbolicExecutionRules { permsExp: Option[ast.Exp], failure: Failure, qvars: Seq[Var], - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (consumeFunction: (State, Heap, Term, Option[ast.Exp], Verifier) => (ConsumptionResult, State, Heap, Option[CH])) (Q: (State, Option[CH], Verifier) => VerificationResult) : VerificationResult = { @@ -520,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)(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) + 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) @@ -571,8 +590,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 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) + 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 1aa582efd..1bf422ad1 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.debugger.DebugExp +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.resources.{FieldID, NonQuantifiedPropertyInterpreter, Resources} @@ -15,13 +16,15 @@ 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 +import viper.silver.dependencyAnalysis.DependencyType import viper.silver.parser.PUnknown import viper.silver.verifier.VerificationError +import scala.annotation.unused import scala.collection.mutable.ListBuffer object moreCompleteExhaleSupporter extends SymbolicExecutionRules { @@ -106,6 +109,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Implies(And(argumentEqualities, IsPositive(ch.perm)), `?s` === ch.snap) }) + val analysisInfos = DependencyAnalysisInfos.create("summarize", DependencyType.Internal) + val taggedSummarisingSnapshot = summarisingSnapshotDefinitions .collectFirst { @@ -120,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) - potentialAlias.filter(c => v.decider.check(IsPositive(c.perm), Verifier.config.checkTimeout())).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 @@ -163,16 +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 analysisInfos = DependencyAnalysisInfos.create("summarise", DependencyType.Internal) + 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(), 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", true))) + 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 = @@ -195,7 +202,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { args: Seq[Term], argsExp: Option[Seq[ast.Exp]], ve: VerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { @@ -203,23 +211,32 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val relevantChunks = findChunksWithID[NonQuantifiedChunk](h.values, id).toSeq if (relevantChunks.isEmpty) { - if (v.decider.checkSmoke(true)) { - if (s.isInPackage) { + 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) } else { 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.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{ + failure + } } } else { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => - v.decider.assert(IsPositive(permSum)) { + v.decider.assert(IsPositive(permSum), analysisInfos) { 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) v1.decider.handleFailedAssertion(IsPositive(permSum), analysisInfos, v1.reportFurtherErrors()) + if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, snap, v1) else failure }) } } @@ -233,14 +250,15 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (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, analysisInfos)(Q) else - summariseHeapAndAssertReadAccess(s, h, resource, perms, args, argsExp, returnSnap, ve, v)(Q) + summariseHeapAndAssertReadAccess(s, h, resource, perms, args, argsExp, returnSnap, ve, v, analysisInfos)(Q) } private def summariseHeapAndAssertReadAccess(s: State, @@ -251,7 +269,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { argsExp: Option[Seq[ast.Exp]], returnSnap: Boolean, ve: VerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -260,19 +279,23 @@ 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)), analysisInfos) { 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) 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) - v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum))) { + v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfos) { 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.handleFailedAssertion(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfos, v.reportFurtherErrors()) + if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, h, None, v) else failure } } } @@ -286,7 +309,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -300,13 +324,16 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (relevantChunks.isEmpty) { // if no permission is exhaled, return none - v.decider.assert(perms === NoPerm) { + v.decider.assert(perms === NoPerm, analysisInfos) { 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.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)) { - 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, analysisInfos)((s1, updatedChunks, optSnap, v2) => { Q(s1, Heap(updatedChunks ++ otherChunks), optSnap, v2) }) } else { @@ -317,8 +344,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, analysisInfos).filter(c => + v.decider.check(IsPositive(c.perm), Verifier.config.checkTimeout(), analysisInfos) ) val sortFunction: (NonQuantifiedChunk, NonQuantifiedChunk) => Boolean = (ch1, ch2) => { @@ -359,15 +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 = 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)), 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())) { + 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()) + moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout(), analysisInfos) } else { newChunks.append(ch) } @@ -380,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)))) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfos)) } val newHeap = Heap(allChunks) @@ -390,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())) { + 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) @@ -398,11 +426,13 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!moreNeeded) { Q(s1, newHeap, condSnap, v1) } else { - v1.decider.assert(pNeeded === NoPerm) { + v1.decider.assert(pNeeded === NoPerm, analysisInfos) { 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.handleFailedAssertion(pNeeded === NoPerm, analysisInfos, v1.reportFurtherErrors()) + if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, newHeap, condSnap, v1) else failure } } }) @@ -410,11 +440,13 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!moreNeeded) { Q(s0, newHeap, None, v) } else { - v.decider.assert(pNeeded === NoPerm) { + v.decider.assert(pNeeded === NoPerm, analysisInfos) { 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(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 } } } @@ -431,7 +463,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, ListBuffer[NonQuantifiedChunk], Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -468,11 +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))) + v.decider.assume(constraint, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), analysisInfos) 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))) + @unused // required in order to ensure a sound dependency analysis + 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 = @@ -484,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) + 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)) { + 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))) + 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)) @@ -502,14 +537,21 @@ 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.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{ + failure + } } } 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, 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]] @@ -537,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) + 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 58c386fe9..62e367b3a 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.DependencyAnalysisInfos 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, analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -23,16 +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), analysisInfos) { 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.handleFailedAssertion(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) + def assertPositive(s: State, tPerm: Term, ePerm: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -40,9 +43,12 @@ 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), analysisInfos) { 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.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/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 65ff902e9..7a823e0a9 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -8,18 +8,20 @@ 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.DependencyAnalysisInfos 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 +import viper.silver.dependencyAnalysis.DependencyType import viper.silver.verifier.PartialVerificationError trait PredicateSupportRules extends SymbolicExecutionRules { @@ -31,7 +33,8 @@ trait PredicateSupportRules extends SymbolicExecutionRules { ePerm: Option[ast.Exp], constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -44,7 +47,8 @@ trait PredicateSupportRules extends SymbolicExecutionRules { constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, v: Verifier, - pa: ast.PredicateAccess) + pa: ast.PredicateAccess, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult } @@ -61,7 +65,8 @@ object predicateSupporter extends PredicateSupportRules { ePerm: Option[ast.Exp], constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -75,33 +80,33 @@ 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, 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))"))) + 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)((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, 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, 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")) - 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, 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 @@ -115,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) + 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) + 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) + 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) + 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) + 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) @@ -144,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, resetState = false)((s1, v1, QB) => { - brancher.branch(s1, substCond, condExp, v1)( + 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, 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, isUnfolding)((s3, v3) => QB(s3, null, v3)) + producePredicateContents(s2, right, toReplace, v2, analysisInfos, isUnfolding)((s3, v3) => QB(s3, null, v3)) } ) }) (entries => { @@ -165,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, analysisInfos)( (s1, v1) => { - producePredicateContents(s1, left, toReplace, v1, isUnfolding)(Q) + producePredicateContents(s1, left, toReplace, v1, analysisInfos, isUnfolding)(Q) }, (s2, v2) => { - producePredicateContents(s2, right, toReplace, v2, isUnfolding)(Q) + producePredicateContents(s2, right, toReplace, v2, analysisInfos, isUnfolding)(Q) } ) } @@ -186,7 +191,8 @@ object predicateSupporter extends PredicateSupportRules { constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, v: Verifier, - pa: ast.PredicateAccess) + pa: ast.PredicateAccess, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -198,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)((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, 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))"))) + 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, @@ -218,14 +224,14 @@ object predicateSupporter extends PredicateSupportRules { v4) }) } else { - produce(s3, toSf(snap.get), body, pve, v1)((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))"))) + 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 2c6f072a9..ca69370b5 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -6,20 +6,22 @@ package viper.silicon.rules -import viper.silicon.debugger.DebugExp import viper.silicon.Config.JoinMode - -import scala.collection.mutable -import viper.silver.ast -import viper.silver.ast.utility.QuantifiedPermissions.QuantifiedPermissionAssertion -import viper.silver.verifier.PartialVerificationError +import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.logger.records.data.{CondExpRecord, ImpliesRecord, ProduceRecord} 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} +import scala.collection.mutable + trait ProductionRules extends SymbolicExecutionRules { /** Produce assertion `a` into state `s`. @@ -29,6 +31,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. @@ -37,7 +40,8 @@ trait ProductionRules extends SymbolicExecutionRules { sf: (Sort, Verifier) => Term, a: ast.Exp, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -53,6 +57,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]] */ @@ -60,7 +65,8 @@ trait ProductionRules extends SymbolicExecutionRules { sf: (Sort, Verifier) => Term, as: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult } @@ -99,18 +105,20 @@ object producer extends ProductionRules { sf: (Sort, Verifier) => Term, a: ast.Exp, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = - produceR(s, sf, a.whenInhaling, pve, v)(Q) + produceR(s, sf, a.whenInhaling, pve, v, analysisInfos)(Q) /** @inheritdoc */ def produces(s: State, sf: (Sort, Verifier) => Term, as: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -125,14 +133,15 @@ object producer extends ProductionRules { allPves ++= pves }) - produceTlcs(s, sf, allTlcs.result(), allPves.result(), v)(Q) + produceTlcs(s, sf, allTlcs.result(), allPves.result(), v, analysisInfos)(Q) } private def produceTlcs(s: State, sf: (Sort, Verifier) => Term, as: Seq[ast.Exp], pves: Seq[PartialVerificationError], - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -142,25 +151,29 @@ object producer extends ProductionRules { val a = as.head.whenInhaling val pve = pves.head + val analysisInfos1 = v.decider.handleAndGetUpdatedAnalysisInfos(analysisInfos, a.info, a) if (as.tail.isEmpty) - wrappedProduceTlc(s, sf, a, pve, v)(Q) + 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) + 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)((s1, v1) => - produceTlcs(s1, sf1, as.tail, pves.tail, v1)(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)) => + case _: IllegalArgumentException if v.decider.check(False, Verifier.config.assertTimeout.getOrElse(0), analysisInfos) => Unreachable() } @@ -172,14 +185,15 @@ object producer extends ProductionRules { sf: (Sort, Verifier) => Term, a: ast.Exp, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (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, analysisInfos)(Q) } /** Wrapper/decorator for consume that injects the following operations: @@ -189,12 +203,22 @@ object producer extends ProductionRules { sf: (Sort, Verifier) => Term, a: ast.Exp, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + 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, analysisInfos) + } + 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)((s1, v1) => { + produceTlc(s, sf, a, pve, v, analysisInfos)((s1, v1) => { v1.symbExLog.closeScope(sepIdentifier) Q(s1, v1)}) } @@ -203,10 +227,10 @@ object producer extends ProductionRules { sf: (Sort, Verifier) => Term, a: ast.Exp, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (continuation: (State, Verifier) => VerificationResult) : VerificationResult = { - v.logger.debug(s"\nPRODUCE ${viper.silicon.utils.ast.sourceLineColumn(a)}: $a") v.logger.debug(v.stateFormatter.format(s, v.decider.pcs)) @@ -218,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)((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, 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) => { + 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))) + 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)`. @@ -251,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)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => produceR(s2, sf, a0, pve, v2)((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", true))) + 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)`. @@ -271,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)((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, 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) => { + 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)((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) })) @@ -299,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)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => produceR(s2, sf, a1, pve, v2)((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)((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)((s1, g1, body, v1) => - produceR(s1.copy(g = s1.g + g1), sf, body, pve, v1)(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)((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, 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() || @@ -330,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)(Q) + v2.heapSupporter.produceSingle(s2, resource, tArgs, eArgsNew, snap, None, gain, gainExp, pve, true, v2, analysisInfos)(Q) }))) @@ -354,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) { + 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)((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) @@ -371,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))) /* 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) + 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 b1a12e07e..b780f4363 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -7,9 +7,11 @@ package viper.silicon.rules import viper.silicon -import viper.silicon.debugger.DebugExp import viper.silicon.Map import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos.DefaultDependencyAnalysisInfos +import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state._ import viper.silicon.logger.records.data.CommentRecord @@ -21,10 +23,11 @@ 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.dependencyAnalysis.{DependencyType, NoDependencyAnalysisMerge} import viper.silver.parser.PUnknown import viper.silver.reporter.InternalWarningMessage import viper.silver.verifier.reasons.{InsufficientPermission, MagicWandChunkNotFound} @@ -187,7 +190,10 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { permissions: Term, permissionsExp: Option[ast.Exp], sm: Term, - program: ast.Program) + program: ast.Program, + v: Verifier, + analysisInfos: DependencyAnalysisInfos, + isExhale: Boolean) : QuantifiedBasicChunk /** Creates a quantified chunk corresponding to the assertion @@ -227,7 +233,9 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { userProvidedTriggers: Option[Seq[Trigger]], qidPrefix: String, v: Verifier, - program: ast.Program) + program: ast.Program, + analysisInfos: DependencyAnalysisInfos, + isExhale: Boolean) : (QuantifiedBasicChunk, InverseFunctions) def splitHeap[CH <: QuantifiedBasicChunk : NotNothing : ClassTag] @@ -239,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): 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 * @@ -284,7 +292,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permissions: Term, permissionsExp: Option[ast.Exp], sm: Term, - program: ast.Program) + program: ast.Program, + v: Verifier, + analysisInfos: DependencyAnalysisInfos, + isExhale: Boolean) : QuantifiedBasicChunk = { val condition = @@ -312,7 +323,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Some(arguments), argumentsExp, hints, - program) + program, + v, + analysisInfos, + isExhale) } /** @inheritdoc [[QuantifiedChunkSupport.createQuantifiedChunk]] */ @@ -334,7 +348,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { userProvidedTriggers: Option[Seq[Trigger]], qidPrefix: String, v: Verifier, - program: ast.Program) + program: ast.Program, + analysisInfos: DependencyAnalysisInfos, + isExhale: Boolean) : (QuantifiedBasicChunk, InverseFunctions) = { val (inverseFunctions, imagesOfCodomain) = @@ -375,7 +391,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { None, None, hints, - program) + program, + v, + analysisInfos, + isExhale) (ch, inverseFunctions) } @@ -417,7 +436,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments: Option[Seq[Term]], optSingletonArgumentsExp: Option[Seq[ast.Exp]], hints: Seq[Term], - program: ast.Program) + program: ast.Program, + v: Verifier, + analysisInfos: DependencyAnalysisInfos, + isExhale: Boolean) : QuantifiedBasicChunk = { resource match { @@ -436,7 +458,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optInverseFunctions, optSingletonArguments.map(_.head), optSingletonArgumentsExp.map(_.head), - hints) + hints, + v.decider.getAnalysisInfo(analysisInfos), + isExhale) case predicate: ast.Predicate => QuantifiedPredicateChunk( @@ -451,7 +475,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optInverseFunctions, optSingletonArguments, optSingletonArgumentsExp, - hints) + hints, + v.decider.getAnalysisInfo(analysisInfos), + isExhale) case wand: ast.MagicWand => val conditionalizedPermissions = Ite(condition, permissions, NoPerm) @@ -466,7 +492,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optInverseFunctions, optSingletonArguments, optSingletonArgumentsExp, - hints) + hints, + v.decider.getAnalysisInfo(analysisInfos), + isExhale) case other => sys.error(s"Found yet unsupported resource $other (${other.getClass.getSimpleName})") @@ -645,18 +673,19 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { smDef: SnapshotMapDefinition, v: Verifier) : (PermMapDefinition, PmCache) = { - - Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { + 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) + 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) + v.decider.assume(valueDef, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, analysisInfos=analysisInfos) (pmDef, s.pmCache + ((resource, relevantChunks) -> pmDef)) } + res } /* Snapshots */ @@ -683,6 +712,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSmDomainDefinitionCondition: Option[Term] = None, optQVarsInstantiations: Option[Seq[Term]] = None) : (SnapshotMapDefinition, SnapshotMapCache) = { + val analysisInfos = DependencyAnalysisInfos.createUnique("summarizing heap", DependencyType.Internal) def emitSnapshotMapDefinition(s: State, smDef: SnapshotMapDefinition, @@ -695,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) + 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 { @@ -707,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) + Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfos=analysisInfos) } } @@ -715,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) + 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 { @@ -727,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) + Option.when(withExp)(DebugExp.createInstance(comment, true)), enforceAssumption = false, analysisInfos=analysisInfos) } } @@ -753,7 +783,6 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } emitSnapshotMapDefinition(s, smDef, v, optQVarsInstantiations) - (smDef, smCache) } @@ -765,7 +794,6 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSmDomainDefinitionCondition: Option[Term] = None, optQVarsInstantiations: Option[Seq[Term]] = None) : (State, SnapshotMapDefinition, PermMapDefinition) = { - val (smDef, smCache) = summarisingSnapshotMap( s, resource, codomainQVars, relevantChunks, v, optSmDomainDefinitionCondition, optQVarsInstantiations) @@ -777,7 +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)) - (s2, smDef, pmDef) } @@ -790,14 +817,12 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { relevantChunks: Seq[QuantifiedBasicChunk], v: Verifier) : (State, PermMapDefinition) = { - val s1 = s val (pmDef, pmCache) = quantifiedChunkSupporter.summarisingPermissionMap( s1, resource, codomainQVars, relevantChunks, null, v) val s2 = s1.copy(pmCache = pmCache) - (s2, pmDef) } @@ -829,7 +854,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { pve: PartialVerificationError, negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -859,7 +885,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { userProvidedTriggers = optTrigger.map(_ => tTriggers), qidPrefix = qid, v = v, - program = s.program) + program = s.program, + analysisInfos = analysisInfos, + isExhale = false) val (effectiveTriggers, effectiveTriggersQVars, effectiveTriggersQVarExps) = optTrigger match { case Some(_) => @@ -904,8 +932,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val commentGlobals = "Nested auxiliary terms: globals" v.decider.prover.comment(commentGlobals) + 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) + enforceAssumption = false, analysisInfos=analysisInfosGlobals) val commentNonGlobals = "Nested auxiliary terms: non-globals" v.decider.prover.comment(commentNonGlobals) @@ -913,13 +942,13 @@ 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, 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) { + v.decider.assert(nonNegTerm, analysisInfos) { case true => /* TODO: Can we omit/simplify the injectivity check in certain situations? */ @@ -941,7 +970,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, analysisInfos) { case true => val ax = inverseFunctions.axiomInversesOfInvertibles val inv = inverseFunctions.copy(axiomInversesOfInvertibles = Forall(ax.vars, ax.body, effectiveTriggers)) @@ -950,8 +979,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, 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 @@ -974,9 +1003,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { triggers = effectiveTriggers, qidPrefix = qid ) - v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp) + 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) + 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. @@ -998,7 +1027,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", true)), analysisInfos.withDependencyType(DependencyType.Trigger)) val newFuncRec = fr1.recordFvfAndDomain(smDef1) (smCache1, newFuncRec) } else { @@ -1010,12 +1039,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.handleFailedAssertion(completeReceiverInjectivityCheck, analysisInfos, 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.handleFailedAssertion(nonNegTerm, analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure + } + } def produceSingleLocation(s: State, @@ -1029,7 +1063,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { ePerm: Option[ast.Exp], resourceTriggerFactory: Term => Term, /* Trigger with some snapshot */ mergeAndTrigger: Boolean, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -1038,19 +1073,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, true))) + 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) + 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) + 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)))) + 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, _) = @@ -1058,7 +1093,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", true)), analysisInfos.withDependencyType(DependencyType.Trigger)) smCache1 } else { s.smCache @@ -1101,7 +1136,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1137,7 +1173,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, analysisInfos=DependencyAnalysisInfos.create(comment, DependencyType.Internal)) val comment2 = "Nested auxiliary terms: non-globals" v.decider.prover.comment(comment2) @@ -1147,10 +1183,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, 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) + 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)) @@ -1158,11 +1194,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) { + v.decider.assert(nonNegTerm, analysisInfos) { case true => val hints = quantifiedChunkSupporter.extractHints(Some(tCond), tArgs) val chunkOrderHeuristics = - qpAppChunkOrderHeuristics(inverseFunctions.invertibles, qvars, hints, v) + 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) @@ -1194,7 +1230,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) { + v.decider.assert(completeReceiverInjectivityCheck, analysisInfos) { case true => val qvarsToInvOfLoc = inverseFunctions.qvarsToInversesOf(formalQVars) val condOfInvOfLoc = tCond.replace(qvarsToInvOfLoc) @@ -1208,8 +1244,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, 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( @@ -1217,7 +1253,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, analysisInfos=analysisInfos) } @@ -1230,7 +1266,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, + analysisInfos)((s2, heap, rPerm, rPermExp, v2) => { val (relevantChunks, otherChunks) = quantifiedChunkSupporter.splitHeap[QuantifiedBasicChunk]( heap, ChunkIdentifier(resource, s.program)) @@ -1247,7 +1284,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPerm, rPermExp, chunkOrderHeuristics, - v2) + v2, + analysisInfos) val optSmDomainDefinitionCondition2 = if (s3.smDomainNeeded) Some(And(condOfInvOfLoc, IsPositive(lossOfInvOfLoc), And(imagesOfFormalQVars))) else None @@ -1278,14 +1316,16 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optTrigger.map(_ => tTriggers), qid, v2, - s.program + s.program, + analysisInfos, + isExhale = true ) - 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, analysisInfos) + v.decider.assume(inverseFunctions.axiomInvertiblesOfInverses, debugExp, analysisInfos) 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, analysisInfos) + v.decider.assume(substitutedAxiomInversesOfInvertibles, debugExp, analysisInfos) val h2 = Heap(remainingChunks ++ otherChunks) val s4 = s3.copy(smCache = smCache2, constrainableARPs = s.constrainableARPs) @@ -1313,7 +1353,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { lossOfInvOfLoc, lossExp, chunkOrderHeuristics, - v + v, + analysisInfos ) permissionRemovalResult match { case (Complete(), s2, remainingChunks) => @@ -1339,12 +1380,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.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 => - 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.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 + } + } 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.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 + } + } } def consumeSingleLocation(s: State, @@ -1359,7 +1419,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { returnSnap: Boolean, optChunkOrderHeuristic: Option[Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk]], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1371,7 +1432,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { heuristics case None => quantifiedChunkSupporter.singleReceiverChunkOrderHeuristic(arguments, - quantifiedChunkSupporter.extractHints(None, arguments), v) + quantifiedChunkSupporter.extractHints(None, arguments), v, analysisInfos) } if (s.exhaleExt) { @@ -1380,7 +1441,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, analysisInfos)((s1, h1, rPerm, rPermExp, v1) => { val (relevantChunks, otherChunks) = quantifiedChunkSupporter.splitHeap[QuantifiedBasicChunk](h1, chunkIdentifier) val (result, s2, remainingChunks) = quantifiedChunkSupporter.removePermissions( @@ -1395,7 +1456,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPerm, rPermExp, chunkOrderHeuristics, - v + v, + analysisInfos ) val h2 = Heap(remainingChunks ++ otherChunks) val (smDef1, smCache1) = @@ -1414,7 +1476,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, analysisInfos, isExhale=true) val s3 = s2.copy(functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef1), smCache = smCache1) (result, s3, h2, Some(consumedChunk)) @@ -1444,7 +1506,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permissions, permissionsExp, chunkOrderHeuristics, - v + v, + analysisInfos ) result match { case (Complete(), s1, remainingChunks) => @@ -1467,11 +1530,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.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, s.h, None, v) else failure } } } @@ -1482,7 +1547,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { condition: Term, perms: Term, permsExp: Option[ast.Exp], - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) : ConsumptionResult = { var permsAvailable: Term = NoPerm @@ -1499,7 +1565,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), 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)())) @@ -1522,7 +1588,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { perms: Term, // p(rs) permsExp: Option[ast.Exp], // p(rs) chunkOrderHeuristic: Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk], - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) : (ConsumptionResult, State, Seq[QuantifiedBasicChunk]) = { val rmPermRecord = new CommentRecord("removePermissions", s, v.decider.pcs) @@ -1539,7 +1606,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, analysisInfos) return (result, s, relevantChunks) } @@ -1603,21 +1670,23 @@ 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, analysisInfos) remainingChunks = - remainingChunks :+ ithChunk.permMinus(ithPTaken, ithPTakenExp) + 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()) + 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()) + val chunkUnused = v.decider.check(unusedCheck, Verifier.config.checkTimeout(), analysisInfos.withDependencyType(DependencyType.Internal)) if (chunkUnused) { remainingChunks = remainingChunks :+ ithChunk } else { remainingChunks = - remainingChunks :+ ithChunk.permMinus(ithPTaken, ithPTakenExp) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(analysisInfos)) } + }else{ + val _ = GeneralChunk.withPerm(ithChunk, ithPTaken, None, v.decider.getAnalysisInfo(analysisInfos), isExhale=true) } } @@ -1630,7 +1699,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(), analysisInfos)) { Complete() } else { Incomplete(ithPNeeded, ithPNeededExp) @@ -1640,7 +1709,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), analysisInfos) /* This check is a must-check, i.e. an assert */) Complete() else success @@ -1959,7 +2028,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, 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))) @@ -2000,11 +2069,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(), analysisInfos) 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, analysisInfos.withDependencyType(DependencyType.Internal)) } result case _ => false @@ -2030,11 +2099,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(), analysisInfos) 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, analysisInfos.withDependencyType(DependencyType.Internal)) } result } else { @@ -2096,7 +2165,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, 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. @@ -2122,7 +2191,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(), analysisInfos) } else { false } @@ -2138,7 +2207,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, 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. @@ -2155,7 +2224,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(), analysisInfos)) case _ => false }).toSeq diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 663282d3c..e18b80875 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -6,9 +6,10 @@ package viper.silicon.rules -import viper.silicon.debugger.DebugExp import viper.silicon.Config import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces.state._ import viper.silicon.logger.records.data.{CommentRecord, SingleMergeRecord} import viper.silicon.resources.{NonQuantifiedPropertyInterpreter, Resources} @@ -19,17 +20,18 @@ 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 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, 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): State - protected def assumeUpperPermissionBoundForQPFields(s: State, heaps: Seq[Heap], v: Verifier): 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 @@ -41,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): (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): (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): 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): 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 @@ -57,8 +59,11 @@ 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 analysisInfos = DependencyAnalysisInfos.createUnique("state consolidation", DependencyType.Internal) v.decider.prover.comment("[state consolidation]") v.decider.prover.saturate(config.proverSaturationTimeouts.beforeIteration) @@ -78,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, _, snapEqs) = singleMerge(functionRecorder, destChunks, newChunks, s.functionRecorderQuantifiedVariables().map(_._1), v) + 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)))) + snapEqs foreach (t => v.decider.assume(t, Option.when(withExp)(DebugExp.createInstance("Snapshot Equations", true)), analysisInfos)) functionRecorder = _functionRecorder mergedChunks = _mergedChunks @@ -98,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(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)))) + 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)))) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfos)) } v.symbExLog.closeScope(sepIdentifier) @@ -114,8 +119,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol h = mergedHeaps.head, reserveHeaps = mergedHeaps.tail) - val s2 = assumeUpperPermissionBoundForQPFields(s1, v) - + val s2 = assumeUpperPermissionBoundForQPFields(s1, v, analysisInfos) s2 } @@ -123,22 +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): (FunctionRecorder, Heap) = { - merge(fr, s, h, Heap(Seq(ch)), v) + 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): (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 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) + 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) + 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)))) + 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) @@ -149,7 +157,8 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol destChunks: Seq[Chunk], newChunks: Seq[Chunk], qvars: Seq[Var], - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) : (FunctionRecorder, Seq[Chunk], Seq[Chunk], @@ -168,10 +177,12 @@ 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 analysisInfos = DependencyAnalysisInfos.createUnique("state_consolidation", DependencyType.Internal) + val res = findMatchingChunk(accMergedChunks, nextChunk, v, analysisInfos) match { case Some(ch) => - mergeChunks(fr1, ch, nextChunk, qvars, v) match { + val resMerge = mergeChunks(fr1, ch, nextChunk, qvars, v, analysisInfos) + + resMerge match { case Some((fr2, newChunk, snapEq)) => (fr2, newChunk +: accMergedChunks.filterNot(_ == ch), newChunk +: accNewChunks, accSnapEqs + snapEq) case None => @@ -180,27 +191,33 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol case None => (fr1, nextChunk +: accMergedChunks, nextChunk +: accNewChunks, accSnapEqs) } + 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, analysisInfos: DependencyAnalysisInfos): 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, 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): Option[(FunctionRecorder, Chunk, Term)] = (chunk1, chunk2) match { + 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) => + (fRec, ch, v.decider.wrapWithDependencyAnalysisLabel(snapEq, Set(chunk1, chunk2)))}) + } + + 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)())), 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`)) @@ -210,14 +227,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.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), 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 } @@ -250,10 +267,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, analysisInfos: DependencyAnalysisInfos): State = + assumeUpperPermissionBoundForQPFields(s, s.h +: s.reserveHeaps, v, analysisInfos) - protected def assumeUpperPermissionBoundForQPFields(s: State, heaps: Seq[Heap], v: Verifier): 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) @@ -285,7 +302,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, 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 @@ -302,7 +319,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, 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 @@ -318,7 +335,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, analysisInfos.withDependencyType(DependencyType.Internal)) } } @@ -390,15 +407,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, analysisInfos: DependencyAnalysisInfos): (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, analysisInfos: DependencyAnalysisInfos): (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, analysisInfos: DependencyAnalysisInfos): 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, analysisInfos: DependencyAnalysisInfos): State = s } /** A variant of [[DefaultStateConsolidator]] that aims to work best when Silicon is run in @@ -418,10 +435,12 @@ class MoreComplexExhaleStateConsolidator(config: Config) extends DefaultStateCon // silver\src\test\resources\quantifiedpermissions\sets\generalised_shape.sil // to fail. + val analysisInfos = DependencyAnalysisInfos.createUnique("state consolidation", DependencyType.Internal) + 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, analysisInfos) } s diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index a4942f237..bd003b7e0 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -7,13 +7,14 @@ 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._ import viper.silicon.rules.InverseFunctions 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 = { @@ -29,7 +30,20 @@ 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], + analysisInfo: AnalysisInfo, isExhale: Boolean=false): BasicChunk = { + analysisInfo.decider.registerChunk[BasicChunk]({finalPerm => + new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, finalPerm, permExp)}, + perm, analysisInfo, isExhale) + } +} + +case class BasicChunk private (resourceID: BaseID, id: BasicChunkIdentifier, args: Seq[Term], argsExp: Option[Seq[ast.Exp]], @@ -45,45 +59,76 @@ case class BasicChunk(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 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]) = + 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]) = + 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 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 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" 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)) } } +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 def withSnapshotMap(snap: Term): 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 protected def withSnapshotMap(snap: Term): QuantifiedBasicChunk def singletonArguments: Option[Seq[Term]] def singletonArgumentExps: Option[Seq[ast.Exp]] 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, + isExhale: Boolean=false): QuantifiedFieldChunk = { + analysisInfo.decider.registerChunk[QuantifiedFieldChunk]({perm => + new QuantifiedFieldChunk(id, fvf, condition, conditionExp, perm, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints)}, + permValue, analysisInfo, isExhale) + } +} + /* 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 * chunks instead. */ -case class QuantifiedFieldChunk(id: BasicChunkIdentifier, +case class QuantifiedFieldChunk private(id: BasicChunkIdentifier, fvf: Term, condition: Term, conditionExp: Option[ast.Exp], @@ -92,7 +137,7 @@ case class QuantifiedFieldChunk(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], @@ -118,28 +163,51 @@ case class QuantifiedFieldChunk(id: BasicChunkIdentifier, Lookup(id.name, fvf, arguments.head) } - 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]) = - 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 withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = + new QuantifiedFieldChunk(id, fvf, condition, conditionExp, newPerm, newPermExp, invs, singletonRcvr, singletonRcvrExp, hints) + override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]) = + 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 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 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 def withSnapshotMap(newFvf: Term) = - QuantifiedFieldChunk(id, newFvf, condition, conditionExp, permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) + 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))) } -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, + isExhale: Boolean=false): QuantifiedPredicateChunk = { + analysisInfo.decider.registerChunk[QuantifiedPredicateChunk]({finalPerm => + new QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, condition, conditionExp, finalPerm, permValueExp, invs, singletonArgs, singletonArgExps, hints)}, + permValue, analysisInfo, isExhale) + } +} + + +case class QuantifiedPredicateChunk private(id: BasicChunkIdentifier, quantifiedVars: Seq[Var], quantifiedVarExps: Option[Seq[ast.LocalVarDecl]], psf: Term, @@ -150,7 +218,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}") @@ -166,28 +234,48 @@ case class QuantifiedPredicateChunk(id: BasicChunkIdentifier, override def valueAt(args: Seq[Term]) = PredicateLookup(id.name, psf, args) - 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]) = - 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 withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = + new QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, condition, conditionExp, newPerm, newPermExp, invs, singletonArgs, singletonArgExps, hints) + override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]) = + 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 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 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 def withSnapshotMap(newPsf: Term) = - QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, newPsf, condition, conditionExp, permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) + 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))) } -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, + isExhale: Boolean=false): QuantifiedMagicWandChunk = { + analysisInfo.decider.registerChunk[QuantifiedMagicWandChunk]({finalPerm => + new QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, finalPerm, permExp, invs, singletonArgs, singletonArgExps, hints)}, + perm, analysisInfo, isExhale) + } +} + +case class QuantifiedMagicWandChunk private(id: MagicWandIdentifier, quantifiedVars: Seq[Var], quantifiedVarExps: Option[Seq[ast.LocalVarDecl]], wsf: Term, @@ -196,7 +284,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}") @@ -210,23 +298,23 @@ 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)())) - 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)())) - 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) + override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = + new QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, newPerm, newPermExp, invs, singletonArgs, singletonArgExps, hints) + override protected def withSnapshotMap(newWsf: Term) = + 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" - 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))) } @@ -248,7 +336,24 @@ object MagicWandIdentifier { } } -case class MagicWandChunk(id: 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, + isExhale: Boolean=false): MagicWandChunk = { + analysisInfo.decider.registerChunk[MagicWandChunk]({finalPerm => + new MagicWandChunk(id, bindings, args, argsExp, snap, finalPerm, permExp)}, + perm, analysisInfo, isExhale) + } +} + +case class MagicWandChunk private(id: MagicWandIdentifier, bindings: Map[ast.AbstractLocalVar, (Term, Option[ast.Exp])], args: Seq[Term], argsExp: Option[Seq[ast.Exp]], @@ -261,21 +366,21 @@ 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 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 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 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) + 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}") } } @@ -288,7 +393,7 @@ case class MagicWandChunk(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/main/scala/state/State.scala b/src/main/scala/state/State.scala index 22043c79a..d3026e0da 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.dependencyAnalysis.AnalysisInfo import viper.silver.ast import viper.silver.cfg.silver.SilverCfg import viper.silicon.common.Mergeable @@ -15,7 +16,7 @@ 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.predef.`?r` import viper.silicon.state.terms.{And, Ite} @@ -326,33 +327,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, @@ -433,15 +434,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 )) } @@ -450,18 +452,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) diff --git a/src/main/scala/supporters/Domains.scala b/src/main/scala/supporters/Domains.scala index bb8916f77..6877d63cf 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.common.collections.immutable.InsertionOrderedSet import viper.silicon.common.collections.immutable.MultiMap._ -import viper.silicon.toMap +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfos, DependencyAnalyzer} 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]() + private var collectedAxioms = InsertionOrderedSet[(Term, DependencyAnalysisInfos)]() private var uniqueSymbols = MultiMap.empty[Sort, DomainFun] /* Lifetime */ @@ -98,10 +99,13 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, } }) + 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) - collectedAxioms += terms.And(tAxPres, tAx) + val enableAnalysis = DependencyAnalyzer.extractEnableAnalysisFromInfo(axiom.info).getOrElse(isAnalysisForDomainEnabled) + collectedAxioms = collectedAxioms.incl((terms.And(tAxPres, tAx), DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.addInfo(axiom.exp.info, axiom.exp).withEnabled(enableAnalysis))) }) }) } @@ -118,10 +122,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.assumeAxiomsWithAnalysisInfo(collectedAxioms, "Domain axioms") } def uniquenessAssumptionsAfterAnalysis: Iterable[Term] = diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 71a2eb44a..3f7b12af5 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -7,18 +7,22 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger -import viper.silver.ast -import viper.silver.components.StatefulComponent -import viper.silver.verifier.errors._ -import viper.silicon.interfaces._ +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} -import viper.silicon.state.{State, Store} import viper.silicon.state.State.OldHeaps -import viper.silicon.verifier.{Verifier, VerifierComponent} +import viper.silicon.state.terms.True +import viper.silicon.state.{State, Store} import viper.silicon.utils.freshSnap -import viper.silicon.Map +import viper.silicon.verifier.{Verifier, VerifierComponent} +import viper.silver.ast +import viper.silver.components.StatefulComponent +import viper.silver.dependencyAnalysis._ +import viper.silver.verifier.errors._ /* TODO: Consider changing the DefaultMethodVerificationUnitProvider into a SymbolicExecutionRule */ @@ -29,9 +33,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] = _ @@ -76,27 +80,44 @@ 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)), 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)) + 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)((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)((_, _) => { + produces(s4, freshSnap, posts, ContractNotWellformed, v3, analysisInfosPostcondition)((_, _) => { symbExLog.closeScope(sepIdentifier) Success()})}) && { executionFlowController.locally(s2a, v2)((s3, v3) => { - exec(s3, body, v3)((s4, v4) => - consumes(s4, posts, false, postViolated, v4)((_, _, _) => - Success()))}) } )})}) + 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, analysisInfosPostcondition)((_, _, _) => + Success())})}) } )})}) + + if(method.body.isEmpty){ + 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]) + + result.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(method.name, _, allErrors, Some(method))) v.decider.resetProverOptions() diff --git a/src/main/scala/supporters/PredicateVerificationUnit.scala b/src/main/scala/supporters/PredicateVerificationUnit.scala index 9e3e4c2a3..65bf372c4 100644 --- a/src/main/scala/supporters/PredicateVerificationUnit.scala +++ b/src/main/scala/supporters/PredicateVerificationUnit.scala @@ -8,21 +8,22 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger import viper.silicon.common.collections.immutable.InsertionOrderedSet -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.DependencyAnalysisInfos +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 @@ -115,7 +116,6 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve var branchResults: Seq[(Seq[Term], Seq[(ast.Exp, Option[ast.Exp])], Heap, InsertionOrderedSet[Term])] = Seq() - val result = predicate.body match { case None => Success() @@ -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)((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 8f3f6efa8..e515c07fa 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.debugger.DebugExp +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 @@ -26,7 +27,8 @@ trait SnapshotSupporter { sf: (Sort, Verifier) => Term, a0: ast.Exp, a1: ast.Exp, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) : ((Sort, Verifier) => Term, (Sort, Verifier) => Term) } @@ -117,10 +119,11 @@ class DefaultSnapshotSupporter(symbolConverter: SymbolConverter) extends Snapsho sf: (Sort, Verifier) => Term, a0: ast.Exp, a1: ast.Exp, - v: Verifier) + v: Verifier, + analysisInfos: DependencyAnalysisInfos) : ((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, analysisInfos) val sf0 = toSf(snap0) val sf1 = toSf(snap1) @@ -128,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): (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 @@ -160,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))) + 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 06f762873..a91fb99e0 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -6,24 +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.common.collections.immutable.InsertionOrderedSet +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfos, DependencyAnalyzer} import viper.silicon.interfaces.FatalResult import viper.silicon.rules.{InverseFunctions, PermMapDefinition, 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.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.dependencyAnalysis._ import viper.silver.parser.PUnknown -import viper.silver.reporter.Reporter +import viper.silver.reporter.{InternalWarningMessage, Reporter} + +import scala.annotation.unused trait FunctionRecorderHandler { @@ -130,18 +132,32 @@ class FunctionData(val programFunction: ast.Function, else Seq.fill(1 + formalArgs.size)(None) + 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) val triggerFunctionApplication = App(statelessFunction, formalArgs.values.toSeq) val preconditionFunctionApplication = App(preconditionFunction, `?s` +: formalArgs.values.toSeq) - val limitedAxiom = - Forall(arguments, + + 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)) + Trigger(functionApplication)), + DependencyAnalysisInfos.create("Limited Axiom", DependencyType.Internal).withEnabled(isAnalysisEnabled)) + + val triggerAxiom: (Quantification, DependencyAnalysisInfos) = + (Forall(arguments, triggerFunctionApplication, Trigger(limitedFunctionApplication)), + DependencyAnalysisInfos.create("Trigger Axiom", DependencyType.Trigger).withEnabled(isAnalysisEnabled)) - val triggerAxiom = - Forall(arguments, triggerFunctionApplication, Trigger(limitedFunctionApplication)) /* * Data collected during phases 1 (well-definedness checking) and 2 (verification) @@ -167,31 +183,36 @@ 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, // 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 }) @@ -216,18 +237,28 @@ class FunctionData(val programFunction: ast.Function, } } - lazy val postAxiom: Option[Term] = { + 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) { val pre = preconditionFunctionApplication - val innermostBody = And(generateNestedDefinitionalAxioms ++ List(Implies(pre, And(translatedPosts)))) val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication) - val body = Let(toMap(bodyBindings), innermostBody) - Some(Forall(arguments, body, Trigger(limitedFunctionApplication))) + def wrapBody(body: Term): Term = Let(toMap(bodyBindings), body) + val analysisInfos = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withEnabled(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{ + val innermostBody = And(generateNestedDefinitionalAxioms ++ List(Implies(pre, And(translatedPosts)))) + Seq((Forall(arguments, wrapBody(innermostBody), Trigger(limitedFunctionApplication)), analysisInfos)) + } } else - None + Seq.empty } /* @@ -281,7 +312,7 @@ class FunctionData(val programFunction: ast.Function, expressionTranslator.translate(program, programFunction, this) } - lazy val definitionalAxiom: Option[Term] = { + 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 => { @@ -296,24 +327,27 @@ class FunctionData(val programFunction: ast.Function, val allTriggers = ( Seq(Trigger(functionApplication)) ++ actualPredicateTriggers) - Forall(arguments, body, allTriggers)}) + (Forall(arguments, body, allTriggers), + bodyAnalysisInfos) + }) } - lazy val bodyPreconditionPropagationAxiom: Seq[Term] = { + 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))) + (Forall(arguments, body, Seq(Trigger(functionApplication))), bodyAnalysisInfos) }) else None bodyPreconditions.toSeq } - lazy val postPreconditionPropagationAxiom: Seq[Term] = { + 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)))) + bodies.map(b => (Forall(arguments, b, Seq(Trigger(limitedFunctionApplication))), + DependencyAnalysisInfos.create("postPreconditionPropagationAxiom", DependencyType.Internal).withEnabled(isAnalysisEnabled))) } else Seq() postPreconditions } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 3cc1c934a..f556f2906 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -7,27 +7,30 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.Logger +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.dependencyAnalysis._ +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter 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.{AnnotationSupporter, PredicateData} import viper.silicon.utils.ast.{BigAnd, simplifyVariableName} -import viper.silicon.verifier.{Verifier, VerifierComponent} import viper.silicon.utils.{freshSnap, toSf} -import viper.silver.ast.LocalVarWithVersion +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.components.StatefulComponent +import viper.silver.dependencyAnalysis._ import viper.silver.parser.PType +import viper.silver.verifier.errors.{ContractNotWellformed, FunctionNotWellformed, PostconditionViolated} import scala.annotation.unused @@ -45,15 +48,15 @@ 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[String, FunctionData] = Map.empty - private var emittedFunctionAxioms: Vector[Term] = Vector.empty + private var emittedFunctionAxioms: Vector[(Term, DependencyAnalysisInfos)] = Vector.empty private var freshVars: Vector[Var] = Vector.empty - private var postConditionAxioms: Vector[Term] = Vector.empty + private var postConditionAxioms: Vector[(Term, DependencyAnalysisInfos)] = Vector.empty private val expressionTranslator = { def resolutionFailureMessage(exp: ast.Positioned, data: FunctionData): String = ( @@ -146,7 +149,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 */ @@ -164,11 +167,17 @@ 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() - res + + 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))) + + Seq(res) } private def handleFunction(sInit: State, function: ast.Function): VerificationResult = { @@ -177,6 +186,11 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver conservingSnapshotGeneration = true, 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, EdgeType.Up)))) + presAssertionNodeForJoin foreach v.decider.dependencyAnalyzer.addAssertionNode + + /* Phase 1: Check well-definedness of the specifications */ checkSpecificationWelldefinedness(s, function) match { case (result1: FatalResult, _) => @@ -187,11 +201,13 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case (result1, phase1data) => emitAndRecordFunctionAxioms(data.limitedAxiom) emitAndRecordFunctionAxioms(data.triggerAxiom) - emitAndRecordFunctionAxioms(data.postAxiom.toSeq: _*) + emitAndRecordFunctionAxioms(data.postAxiom: _*) emitAndRecordFunctionAxioms(data.postPreconditionPropagationAxiom: _*) - this.postConditionAxioms = this.postConditionAxioms ++ data.postAxiom.toSeq + this.postConditionAxioms = this.postConditionAxioms ++ data.postAxiom if (function.body.isEmpty) { + decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) + decider.dependencyAnalyzer.addDependenciesForAbstractMembers(function.pres.flatMap(_.topLevelConjuncts), function.posts.flatMap(_.topLevelConjuncts), DependencyAnalysisInfos.DefaultDependencyAnalysisInfos) result1 } else { /* Phase 2: Verify the function's postcondition */ @@ -226,19 +242,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 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)((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)((s2, _) => { + produces(s1, freshSnap, posts, ContractNotWellformed, v, analysisInfosPostcondition)((s2, _) => { recorders :+= s2.functionRecorder Success()})})}) @@ -261,22 +280,30 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver var recorders: Seq[FunctionRecorder] = Vector.empty val wExp = evaluator.withExp + decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) + + val precondAnalysisSourceInfos = DependencyAnalysisInfos.create("preconditions", DependencyType.Internal) + val analysisInfosPostcondition = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)) + val analysisInfosBody = v.decider.handleAndGetUpdatedAnalysisInfos(DependencyAnalysisInfos.DefaultDependencyAnalysisInfos, body.info, body) + .withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(body), JoinType.Source, EdgeType.Down)) 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, Option.when(wExp)(DebugExp.createInstance(s"precondition of ${function.name}", pcsPreExp.get)), enforceAssumption = false) + 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)))), 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, precondAnalysisSourceInfos) v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) - eval(s1, body, FunctionNotWellformed(function), v)((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) - consumes(s2, posts, false, postconditionViolated, v)((s3, _, _) => { + decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, analysisInfosBody) + consumes(s2, posts, false, postconditionViolated, v, analysisInfosPostcondition)((s3, _, _) => { recorders :+= s3.functionRecorder Success()})})})} @@ -285,9 +312,15 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver result } - private def emitAndRecordFunctionAxioms(axiom: Term*): Unit = { - decider.prover.assumeAxioms(InsertionOrderedSet(axiom), "Function axioms") - emittedFunctionAxioms = emittedFunctionAxioms ++ axiom + private def emitAndRecordFunctionAxioms(axiom: (Term, DependencyAnalysisInfos)*): Unit = { + val cleanAxiom = + 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)) + decider.prover.assumeAxiomsWithAnalysisInfo(InsertionOrderedSet(cleanAxiom), "Function axioms") + + emittedFunctionAxioms = emittedFunctionAxioms ++ cleanAxiom } private def generateFunctionSymbolsAfterVerification: Iterable[Either[String, Decl]] = { @@ -319,10 +352,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 f650ecac9..46d7f4e87 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -6,19 +6,14 @@ 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.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.dependencyAnalysis.graphInterpretation.DependencyGraphTestSupporter import viper.silicon.extensions.ConditionalPermissionRewriter import viper.silicon.interfaces._ import viper.silicon.interfaces.decider.ProverLike @@ -26,17 +21,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.{AnnotationSupporter, DefaultDomainsContributor, DefaultMapsContributor, DefaultMultisetsContributor, DefaultPredicateVerificationUnitProvider, DefaultSequencesContributor, DefaultSetsContributor, MagicWandSnapFunctionsContributor, PredicateData} -import viper.silicon.supporters.qps._ +import viper.silicon.supporters._ import viper.silicon.supporters.functions.{DefaultFunctionVerificationUnitProvider, FunctionData} +import viper.silicon.supporters.qps._ 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) @@ -127,6 +130,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) @@ -225,8 +233,10 @@ class DefaultMainVerifier(config: Config, val startTime = System.currentTimeMillis() 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}`." @@ -242,8 +252,10 @@ class DefaultMainVerifier(config: Config, val startTime = System.currentTimeMillis() 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}`." @@ -277,8 +289,10 @@ class DefaultMainVerifier(config: Config, val startTime = System.currentTimeMillis() 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}`." @@ -325,11 +339,13 @@ class DefaultMainVerifier(config: Config, } reporter report VerificationTerminationMessage() - val verificationResults = ( functionVerificationResults - ++ predicateVerificationResults - ++ methodVerificationResults) + val verificationResults = (functionVerificationResults + ++ predicateVerificationResults + ++ methodVerificationResults) + + runDependencyAnalysisWorkflow(verificationResults, program, inputFile) - if (Verifier.config.enableDebugging()){ + if (Verifier.config.startDebuggerAutomatically()){ val debugger = new SiliconDebugger(verificationResults, identifierFactory, reporter, FrontendStateCache.resolver, FrontendStateCache.pprogram, FrontendStateCache.translator, this) debugger.startDebugger() } @@ -633,4 +649,52 @@ 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 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.toSet) + + if (Verifier.config.dependencyAnalysisExportPath.isDefined) { + 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.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() + } + + reporter match { + case analysisReporter: DependencyAnalysisReporter => + analysisReporter.dependencyGraphInterpretersPerMember = dependencyGraphInterpreters + analysisReporter.joinedDependencyGraphInterpreter = Some(result.getFullDependencyGraphInterpreter) + case _ => + } + + + } } diff --git a/src/main/scala/verifier/VerificationPoolManager.scala b/src/main/scala/verifier/VerificationPoolManager.scala index f694fb4ca..1e5749f9e 100644 --- a/src/main/scala/verifier/VerificationPoolManager.scala +++ b/src/main/scala/verifier/VerificationPoolManager.scala @@ -6,15 +6,19 @@ 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.common.collections.immutable.InsertionOrderedSet +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos 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.components.StatefulComponent +import viper.silver.dependencyAnalysis.AnalysisSourceInfo +import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType + +import java.util.concurrent._ class VerificationPoolManager(mainVerifier: MainVerifier) extends StatefulComponent { private val numberOfWorkers: Int = Verifier.config.numberOfParallelVerifiers() @@ -27,7 +31,9 @@ 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)) + 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/resources/dependencyAnalysisTests/README.md b/src/test/resources/dependencyAnalysisTests/README.md new file mode 100644 index 000000000..c2e46b9d9 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/README.md @@ -0,0 +1,35 @@ +# 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/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. + +## 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/DependencyAnalysisPrecisionBenchmark.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)`. + + +The manually crafted examples correspond to programs found in folders `all` and `real-world-examples`. + +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/resources/dependencyAnalysisTests/all/aliasing.vpr b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr new file mode 100644 index 000000000..cd715d84f --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr @@ -0,0 +1,48 @@ +field f: Int + + +method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) + 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(@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 @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)) +{ + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L13") + x.f := n + 1 + + var y: Ref + + @dependencyInfo("assumptionType:SourceCode, label:L14") + y := x + + @dependencyInfo("assertionType:Explicit, label:L15, expectedDependencies:[L10,L13,L14]") + assert y.f > n +} + + +method aliasing2(x: Ref, y: Ref, n: Int) + 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(@dependencyInfo("assumptionType:PathCondition, label:L20")((x == y))){ + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L21, expectedDependencies:[L16,L17,L20]") + x.f := n + 1 + } +} + + diff --git a/src/test/resources/dependencyAnalysisTests/all/branches.vpr b/src/test/resources/dependencyAnalysisTests/all/branches.vpr new file mode 100644 index 000000000..93a6f4548 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/branches.vpr @@ -0,0 +1,78 @@ +method branches1(a: Int, b: Int) +{ + var n:Int, c: Bool + + assume @dependencyInfo("assumptionType:Explicit, label:L1")(0 < a) + assume @dependencyInfo("assumptionType:Explicit, label:L2")(b > 4) + + 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(@dependencyInfo("assumptionType:PathCondition, label:L6")(c)){ + @dependencyInfo("assumptionType:SourceCode, label:L7") + n := a + 1 + }else{ + @dependencyInfo("assumptionType:SourceCode, label:L8") + n := b + 1 + } + + @dependencyInfo("assertionType:Explicit, label:L9, expectedDependencies:[L1,L2,L7,L8]") + assert n > 1 +} + +method branches2(a: Int, b: Int) +{ + var n:Int, c: Bool + + 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(@dependencyInfo("assumptionType:PathCondition, label:L14")(((a >= n)))){ + @dependencyInfo("assumptionType:SourceCode, label:L15") + x := a + b + }else{ + @dependencyInfo("assumptionType:SourceCode, label:L16") + x := n + 1 + @dependencyInfo("assertionType:Explicit, label:L17, expectedDependencies:[L10,L14,L16]") + assert x > 1 + } + + @dependencyInfo("assertionType:Explicit, label:L22, expectedDependencies:[L16,L15,L14,L12]") + assert x > n +} + + +method nestedBranches1(a: Int, b: Int) +{ + var n:Int, c: Bool + + 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(@dependencyInfo("assumptionType:PathCondition, label:L28")(((c)))){ + if(@dependencyInfo("assumptionType:PathCondition, label:L29")(((a > b)))){ + @dependencyInfo("assumptionType:SourceCode, label:L30") + n := a - b + }else{ + @dependencyInfo("assumptionType:SourceCode, label:L31") + n := a + b + } + @dependencyInfo("assumptionType:SourceCode, label:L32") + n := n - 1 + }else{ + @dependencyInfo("assumptionType:SourceCode, label:L33") + n := a + b + } + + @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 new file mode 100644 index 000000000..88a2711e8 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr @@ -0,0 +1,58 @@ +field f: Int + +method sum(x: Int, y: Int) returns(res: Int) + 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 @dependencyInfo("assumptionType:Explicit, label:L6")(x > 100) + @dependencyInfo("assumptionType:SourceCode, label:L7") + res := x + y +} + +method divBy0() +{ + var x: Int + var y: Int + assume @dependencyInfo("assumptionType:Explicit, label:L8")(y > 0) + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L9, expectedDependencies:[L8]") + x := x/y +} + +method basic() +{ + var x: Int + var y: Int + 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 + @dependencyInfo("assertionType:Explicit, label:L14, expectedDependencies:[L11,L12,L13]") + assert x >= 1 +} + + +method sumClient(x: Int, y: Int) +{ + assume @dependencyInfo("assumptionType:Explicit, label:L15")(x >= 0) + assume @dependencyInfo("assumptionType:Explicit, label:L17")(x < y) + var n: Int + @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 + @dependencyInfo("assumptionType:SourceCode,assertionType:SourceCode, label:L19, expectedDependencies:[L15,L17]") + arg1 := x/y + @dependencyInfo("assumptionType:MethodCall,assertionType:MethodCall, label:L20, expectedDependencies:[L15,L17,L19]") + n2 := sum(arg1, y) + @dependencyInfo("assumptionType:SourceCode, label:L21") + n := n + n2 + + @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 new file mode 100644 index 000000000..bab00300a --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr @@ -0,0 +1,52 @@ +field f: Int + +function sum(x: Int, y: Int) : 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]")(result == x + y) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L5, expectedDependencies:[L6,L2,L3,L11,L12,L19,L20]")(result >= 0) +{ + @dependencyInfo("assumptionType:FunctionBody, label:L6")(x + y) +} + +function sumUnverified(x: Int, y: Int): 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]")(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 + assume @dependencyInfo("assumptionType:Explicit, label:L11")(x > 10) + assume @dependencyInfo("assumptionType:Explicit, label:L12")(y > 0) + + @dependencyInfo("assumptionType:SourceCode, label:L13, expectedDependencies:[L11,L12]") + z := sum(x, y) + + @dependencyInfo("assertionType:Explicit, label:L14, expectedDependencies:[L11,L12,L13,L6]") + assert z == x + 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:SourceCode, 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, 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/infeasible.vpr b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr new file mode 100644 index 000000000..5965bd08e --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr @@ -0,0 +1,90 @@ +field f: Int + +method infeasibleBranch1(a: Int, b: Int) + requires @dependencyInfo("assumptionType:Precondition, label:L2")(a > 0) +{ + if(@dependencyInfo("assumptionType:PathCondition, label:L3")(a < 0)){ + @dependencyInfo("assertionType:Explicit, label:L4, expectedDependencies:[L2,L3]") + assert false + } +} + +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) +{ + if(@dependencyInfo("assumptionType:PathCondition, label:L6")(a < 0)){ + assume @dependencyInfo("assumptionType:Explicit, label:L7")(res < 0) + }else{ + assume @dependencyInfo("assumptionType:Explicit, label:L8")(res > 0) + } + @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 @dependencyInfo("assumptionType:Precondition, label:L10")(acc(a.f, 1/2)) + requires @dependencyInfo("assumptionType:Precondition, label:L11, expectedDependencies:[L10]")(a.f > 0) +{ + 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 @dependencyInfo("assumptionType:Precondition, label:L14")(a > 0) +{ + var res: Int + if(@dependencyInfo("assumptionType:PathCondition, label:L15")(a < 0)){ + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L16, expectedDependencies:[L14,L15]") + res := b.f + 1 + @dependencyInfo("assertionType:Explicit, label:L17, expectedDependencies:[L14,L15]") + assert false + } +} + +method implicationTest() +{ + var a: Int + @dependencyInfo("assumptionType:SourceCode, label:L18") + a := 10 + @dependencyInfo("assertionType:Explicit, label:L19, expectedDependencies:[L18]") + assert a == 0 ==> false +} + +method implicationTest2() +{ + var a: Int + var x: Ref + @dependencyInfo("assumptionType:SourceCode, label:L20") + a := 10 + @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/method-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr new file mode 100644 index 000000000..d668317a1 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr @@ -0,0 +1,57 @@ +field f: Int + +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) +{ + @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 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 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/quantified-perm.vpr b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr new file mode 100644 index 000000000..05571149f --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr @@ -0,0 +1,24 @@ +field f: Int +field first : Ref +field second : Ref + + + +method quantifiedSum(nodes: Set[Ref], x: Ref) + 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 @dependencyInfo("assumptionType:Precondition, label:L18")(forall n:Ref :: { n.f } n in nodes ==> + acc(n.f) && 0 <= n.f && n.f <= 100) + requires @dependencyInfo("assumptionType:Precondition, label:L20")((x in nodes)) +{ + var a: Int + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L21") + a := x.f + 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 + } + @dependencyInfo("assertionType:Explicit, label:L24, expectedDependencies:[L21,L23,L20,L15,L18,L22]") + assert a >= 0 +} diff --git a/src/test/resources/dependencyAnalysisTests/all/sequences.vpr b/src/test/resources/dependencyAnalysisTests/all/sequences.vpr new file mode 100644 index 000000000..4fa512775 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/sequences.vpr @@ -0,0 +1,31 @@ + +method sequenceUpdate(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) + requires @dependencyInfo("assumptionType:Precondition, label:L1")((|xs| > 5)) + requires @dependencyInfo("assumptionType:Precondition, label:L2")((|ys| > 3)) +{ + 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) + + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L5, expectedDependencies:[L1]") + res := xs[0 := 1] + + @dependencyInfo("assertionType:Explicit, label:L6, expectedDependencies:[L5,L1]") + assert res[0] == 1 + + @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 @dependencyInfo("assumptionType:Precondition, label:L13")((|xs| > 5)) + requires @dependencyInfo("assumptionType:Precondition, label:L14")((|ys| > 3)) +{ + 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) + + @dependencyInfo("assumptionType:SourceCode, label:L17") + res := xs[0 := 1] + + @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 new file mode 100644 index 000000000..7d4f727e7 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr @@ -0,0 +1,42 @@ +field f: Int + +method stateConsolidation(x: Ref, y: Ref, z: Ref) + 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 + inhale @dependencyInfo("assumptionType:Explicit, label:L5")(acc(a.f, 1/2)) + + assume @dependencyInfo("assumptionType:Explicit, label:L6")(a == x) + + @dependencyInfo("assertionType:Explicit, label:L7, expectedDependencies:[L2,L3,L5,L6]") + assert a != y // triggers state consolidation + + @dependencyInfo("assertionType:Explicit, label:L13, expectedDependencies:[L2,L5,L6]") + assert perm(x.f) == write +} + + +method stateConsolidation3(x: Ref, y: Ref, z: Ref) + 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 + inhale @dependencyInfo("assumptionType:Explicit, label:L17")(acc(x.f, 1/2)) + + @dependencyInfo("assertionType:Explicit, label:L18, expectedDependencies:[L14,L17]") + assert perm(x.f) == write +} + + +method noAlias(a: Ref, b: Ref, c: Ref) + 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))) +{ + + @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 new file mode 100644 index 000000000..67c24a364 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr @@ -0,0 +1,48 @@ +field f: Int + +method sum(n: Int) returns (res: Int) + 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) +{ + @dependencyInfo("assumptionType:SourceCode, label:L4") + res := 0 + var i: Int + @dependencyInfo("assumptionType:SourceCode, label:L5") + i := 0; + 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)) + { + @dependencyInfo("assumptionType:SourceCode, label:L9") + res := res + i + @dependencyInfo("assumptionType:SourceCode, label:L10") + i := i + 1 + } + + @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 @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) +{ + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L16, expectedDependencies:[L12]") + res.f := 0 + var i: Int + @dependencyInfo("assumptionType:SourceCode, label:L17") + i := 0 + 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)) + { + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L22, expectedDependencies:[L12]") + res.f := res.f + i + @dependencyInfo("assumptionType:SourceCode, label:L23") + i := i + 1 + } +} \ 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..38e83d78a --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/unfolding.vpr @@ -0,0 +1,41 @@ +field f: Int + +predicate pred(x: Ref) +{ + acc(x.f) && x.f > 0 +} + + method foo(x: Ref) returns(res: Int) + 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) + } + + +method client1(x: Ref) +{ + var b: Int + 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) + + @dependencyInfo("assumptionType:MethodCall, label:L12, expectedDependencies:[L9,L10,L11]") + b := foo(x) + + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L13, expectedDependencies:[L9,L10,L11,L3,L6,L8,L12]") + b := unfolding pred(x) in x.f + + @dependencyInfo("assertionType:Explicit, label:L14, expectedDependencies:[L9,L10,L11,L3,L6,L8,L12,L13]") + assert b > 0 + + @dependencyInfo("assertionType:Explicit, label:L15, expectedDependencies:[L9,L10,L11,L3,L6,L8,L12]") + exhale pred(x) + } 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/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/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/real-world-examples/gaussian.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/gaussian.vpr new file mode 100644 index 000000000..177a9e277 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/real-world-examples/gaussian.vpr @@ -0,0 +1,81 @@ +field f: Int + +method gaussianSimple(n: Int) returns (res: Int) + requires 0 <= n + requires @irrelevant("Precondition")(n <= 5) +{ + res := 0 + var i: Int := 0 + 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("SourceCode") + res := res + i + @dependency("SourceCode") + i := i + 1 + } + + @testAssertion("Explicit") + assert res == n * (n + 1) / 2 +} + +method gaussianPerm(a: Ref, p: Perm) returns (res: Int) + 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) +{ + res := 0 + var i: Int := 0 + while(@dependency("PathCondition")(i <= a.f)) + invariant @dependency("LoopInvariant")(acc(a.f, p)) + invariant 0 <= a.f && a.f <= 5 + invariant @dependency("LoopInvariant")(i <= (a.f + 1)) + invariant i <= 6 + invariant @dependency("LoopInvariant")(res == (i - 1) * i / 2) + { + @dependency("SourceCode") + res := res + i + @dependency("SourceCode") + i := i + 1 + } + + @testAssertion("Explicit") + 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 + @dependency("SourceCode") + i := 0 + @dependency("Rewrite") + fold gaussianEq(res, i) + while(i <= n) + invariant i <= (n + 1) + invariant i <= 6 + invariant @dependency("LoopInvariant")(gaussianEq(res, i)) + { + @dependency("Rewrite") + unfold gaussianEq(res, i) + @dependency("SourceCode") + res := res + i + @dependency("SourceCode") + i := i + 1 + @dependency("Rewrite") + fold gaussianEq(res, i) + } + assert i == n+1 + @testAssertion("Explicit") + assert gaussianEq(res, n+1) +} 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.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr new file mode 100644 index 000000000..039a9d5e4 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr @@ -0,0 +1,110 @@ +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 @irrelevant("Precondition")(list(this)) + requires @irrelevant("Precondition")(0 <= e && e < 100) + ensures list(this) +{ + @irrelevant("Rewrite") + unfold list(this) + @irrelevant("Explicit") + assume 0 <= this.elem && this.elem < 100 + + if (@irrelevant("PathCondition")(this.next == null)) { + var n: Ref + + @dependency("SourceCode") + n := new(elem, next) + @irrelevant("SourceCode") + n.elem := e + @dependency("SourceCode") + n.next := null + @irrelevant("SourceCode") + this.next := n + @testAssertion("Implicit") + fold list(n) + } else { + var l_next: Ref := this.next + appendList1(l_next, e) + } + + fold list(this) +} + +method appendList2(this: Ref, e: Int) + requires @dependency("Precondition")(list(this)) + requires @dependency("Precondition")(0 <= e && e < 100) + ensures list(this) +{ + @dependency("Rewrite") + unfold list(this) + @irrelevant("Explicit") + assume 0 <= this.elem && this.elem < 100 + + if (@dependency("PathCondition")(this.next == null)) { + var n: Ref + + @irrelevant("SourceCode") + n := new(elem, next) + @irrelevant("SourceCode") + n.elem := e + @irrelevant("SourceCode") + n.next := null + @irrelevant("SourceCode") + this.next := n + @irrelevant("Rewrite") + fold list(n) + } else { + var l_next: Ref := this.next + @testAssertion("Implicit") + appendList2(l_next, e) + } + + fold list(this) +} + +method appendListFull(this: Ref, e: Int) + requires @dependency("Precondition")(list(this)) + requires @dependency("Precondition")(0 <= e && e < 100) + ensures list(this) +{ + @dependency("Rewrite") + unfold list(this) + @irrelevant("Explicit") + assume 0 <= this.elem && this.elem < 100 + + if (@dependency("PathCondition")(this.next == null)) { + var n: Ref + + @dependency("SourceCode") + n := new(elem, next) + @irrelevant("SourceCode") + n.elem := e + @dependency("SourceCode") + n.next := null + @dependency("SourceCode") + this.next := n + @dependency("Rewrite") + fold list(n) + } else { + var l_next: Ref := this.next + @dependency("MethodCall") + appendListFull(l_next, e) + } + + @testAssertion("Implicit") + fold list(this) +} 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/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 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/unitTests/A-inhaleExhale.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/A-inhaleExhale.vpr new file mode 100644 index 000000000..cfdd567f5 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/A-inhaleExhale.vpr @@ -0,0 +1,45 @@ +field f: Int + +method exhaleInhale(a: Ref) + requires @dependency("Precondition")(acc(a.f)) +{ + exhale acc(a.f, 1/2) + inhale acc(a.f, 1/2) + + // $PrecisionTest: $READ_WRITE=a.f $ACC_INVARIANT=acc(a.f) + + @testAssertion("Explicit") + assert perm(a.f) == write +} + +method exhale1(){ + var x: Ref + + @dependency("Explicit") + inhale acc(x.f) + @dependency("Explicit") + inhale x.f > 0 + + exhale 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 +} + +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) + + // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 $ACC_INVARIANT=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/B-permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/B-permissions.vpr new file mode 100644 index 000000000..8496aab60 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/B-permissions.vpr @@ -0,0 +1,105 @@ +field f: Int + +method currentPerm(x: Ref) + requires @dependency("Precondition")(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 +} + +method perm1(){ + var x: Ref + @dependency("SourceCode") + x := new(f) + + // $PrecisionTest: $READ_WRITE=x.f $ACC_INVARIANT=acc(x.f) + + @testAssertion("Explicit") + inhale x.f > 0 +} + +method perm2(){ + var x: Ref + @dependency("Explicit") + inhale acc(x.f, 1/2) + + // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=acc(x.f,1/4) + + @testAssertion("Explicit") + inhale x.f > 0 +} + +method perm3(){ + var x: Ref + @dependency("Explicit") + inhale acc(x.f) + + // $PrecisionTest: $READ_WRITE=x.f $ACC_INVARIANT=acc(x.f) + + @testAssertion("Implicit") + x.f := 5 +} + +method perm4(){ + var x: Ref + @dependency("Explicit") + inhale acc(x.f) + + @dependency("Explicit") + inhale x.f > 0 + + @dependency("SourceCode") + x.f := x.f + 1 + + // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 $ACC_INVARIANT=acc(x.f,1/4) + + @testAssertion("Explicit") + assert x.f > 1 +} + +method perm5(){ + var x: Ref + @dependency("Explicit") + inhale acc(x.f) + @irrelevant("SourceCode") + x.f := x.f + 1 + + // $PrecisionTest: $READ_WRITE=x.f $ACC_INVARIANT=acc(x.f) + + @testAssertion("Explicit") + exhale acc(x.f) +} + +method permAmount1(x: Ref, p: Perm) + requires @dependency("Precondition")(p > none) + requires @dependency("Precondition")(acc(x.f, p)) + { + @dependency("Explicit") + assume p > 1/2 + + // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=p>1/2&&acc(x.f,p/2) + + @testAssertion("Explicit") + exhale acc(x.f, 1/2) + } + + method permAmount2(x: Ref, p: Perm) + requires p > none + requires @dependency("Precondition")(acc(x.f, p)) + { + @dependency("Explicit") + 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 + + exhale acc(x.f, 1/2) + @testAssertion("Explicit") + assert x.f > 0 + } + + + diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/C-permWildcard.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/C-permWildcard.vpr new file mode 100644 index 000000000..5a3e8ed6c --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/C-permWildcard.vpr @@ -0,0 +1,39 @@ +field f: Int +field g: Int + +method wildcardPerm(x: Ref, y: Ref) + 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) + + @testAssertion("Implicit") + inhale x.f > 0 +} + + +method wildcardPermDistinctFields(x: Ref, y: Ref) + 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) + + @testAssertion("Implicit") + inhale x.f > 0 +} + +method quantifiedPermWildcard(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") + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) + + // $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 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/D-quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/D-quantifiedPermissions.vpr new file mode 100644 index 000000000..52ba571c1 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/D-quantifiedPermissions.vpr @@ -0,0 +1,148 @@ +field f: Int + +method quantifiedPerm1(xs: Seq[Ref]) { + assume @dependency("Explicit")(|xs| > 5) + inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) + + inhale xs[0] != xs[1] + + var i: Int + @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) + + @testAssertion() + xs[0].f := 10 +} + +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] + + // $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 +} + +method quantifiedPerm3(xs: Seq[Ref], y: Ref) { + assume @dependency("Explicit")(|xs| > 5) + 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 $ACC_INVARIANT=(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f)) + + @testAssertion("Explicit") + assert xs[0] != y +} + +method quantifiedExhalePartiallyTest(xs: Seq[Ref]) { + var res: Int + assume |xs| > 5 + inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) + + inhale xs[0] != xs[4] + + var i: Int + @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) + + @testAssertion("Explicit") + exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) +} + +method quantifiedExhalePartially(xs: Seq[Ref]) { + var res: Int + 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) + + var i: Int + @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) + + @testAssertion("Implicit") + res := xs[1].f + 1 +} + +method quantifiedExhaleFully(xs: Seq[Ref]) { + assume |xs| > 5 + inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) + + 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) + + @testAssertion("Explicit") + exhale forall x: Ref :: x in xs ==> acc(x.f) +} + +method quantifiedPermWrite1(xs: Seq[Ref]) { + @dependency("Explicit") + assume |xs| > 5 + @dependency("Explicit") + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + + 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)) + + @testAssertion("Implicit") + xs[0].f := 0 +} + +method quantifiedPermWrite2(xs: Seq[Ref]) { + @dependency("Explicit") + assume |xs| > 5 + @dependency("Explicit") + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + + @dependency("SourceCode") + xs[0].f := 0 + + 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)) + + @testAssertion("Explicit") + assert xs[0].f == 0 +} + +method quantifiedPermWrite3(xs: Seq[Ref]) { + @dependency("Explicit") + assume |xs| > 5 + @dependency("Explicit") + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + + @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)) + + @testAssertion("Explicit") + assert xs[0] != xs[1] ==> xs[1].f > 0 +} + +method quantifiedPermWrite4(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency("Precondition")(|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] + + // $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 +} diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr new file mode 100644 index 000000000..89f6d22e8 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr @@ -0,0 +1,88 @@ +field f: Int + +function sum(x: Int, y: Int): Int + requires x >= 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("SourceCode") + 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("SourceCode") + n := sum(x, y) + + // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n $INVARIANT=x>0 + + @dependency("SourceCode") + n2 := sum(x, y) + @dependency("SourceCode") + 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("SourceCode") + n := sumUnverified(x, y) + + // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y $INVARIANT=x>0 + + @irrelevant("SourceCode") + n2 := sumUnverified(x, z) + + @testAssertion("Explicit") + assert n == x + y +} diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr new file mode 100644 index 000000000..e7358e062 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr @@ -0,0 +1,109 @@ +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 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") + 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("MethodCall") + 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("MethodCall") + n := sumUnverified(x, y) + + // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n $INVARIANT=x>0 + + @dependency("MethodCall") + n2 := sumUnverified(x, y) + @dependency("SourceCode") + 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("MethodCall") + n := sum(x, y) + + // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y $INVARIANT=x>0 + + @irrelevant("MethodCall") + n2 := sum(x, z) + + @testAssertion("Explicit") + assert n == x + y +} + +method absClient1(x: Ref) + requires @dependency("Precondition")(acc(x.f)) + requires @irrelevant("Precondition")(x.f > 0) +{ + @dependency("MethodCall") + abs(x) + + @testAssertion("Explicit") + assert x.f >= 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/G-predicates.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/G-predicates.vpr new file mode 100644 index 000000000..66e632ea0 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/G-predicates.vpr @@ -0,0 +1,139 @@ +field f: Int + +predicate greater0(n: Int){ + n > 0 +} + +predicate greater5(n: Int){ + n > 5 +} + +method foldP(n: Int) + requires @dependency("Precondition")(n > 10) +{ + var x: Int + @dependency("SourceCode") + x := 1 + + // $PrecisionTest: $READ_ONLY=n,x $INVARIANT=n>0 + + @testAssertion("Implicit") + fold greater0(x + n) +} + +method unfoldP(n: Int) + requires @dependency("Precondition")(greater0(n)) +{ + var x: Int + @dependency("SourceCode") + x := 1 + @dependency("Rewrite") + unfold greater0(n) + @dependency("SourceCode") + x := n + x + + // $PrecisionTest: $READ_ONLY=x,n $INVARIANT=x>n + + @testAssertion("Explicit") + assert x > 1 +} + +method unfoldFoldP(a: Int, b: Int) + requires @dependency("Precondition")(greater0(a)) && @dependency("Precondition")(greater5(b)) + ensures greater5(a + b) +{ + @dependency("Rewrite") + unfold greater0(a) + @dependency("Rewrite") + unfold greater5(b) + + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=b>0 + + @testAssertion("Implicit") + fold greater5(a + b) +} + +method callWithPredicate(a: Int, b: Int) + requires @dependency("Precondition")(greater0(a)) && @dependency("Precondition")(greater5(b)) + ensures greater5(a + b) +{ + @dependency("MethodCall") + unfoldFoldP(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) +} + +method unfoldPrecision(a: Int, b: Int) + requires @irrelevant("Precondition")(greater0(a)) + requires @dependency("Precondition")(greater5(b)) + ensures greater0(b) +{ + + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=(unfolding$_$greater0(a)$_$in$_$a>0) $ACC_INVARIANT=greater0(a) + + @irrelevant("Rewrite") + unfold greater0(a) + @dependency("Rewrite") + unfold greater5(b) + + @testAssertion("Implicit") + fold greater0(b) +} + +method foldPrecision(a: Int, b: Int) + requires @irrelevant("Precondition")(a > 5) + requires @dependency("Precondition")(b > 5) +{ + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=b>2 + + @testAssertion("Implicit") + fold greater0(b) +} + +method unfolding1(n: Int) + requires @dependency("Precondition")(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("Precondition")(implPred(a, x)) +{ + @dependency("Rewrite") + unfold implPred(a, x) + if(@dependency("PathCondition")(a > 5)){ + @irrelevant("SourceCode") + 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("Precondition")(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("SourceCode") + res := a + } +} diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/H-magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/H-magicWands.vpr new file mode 100644 index 000000000..23fc2fa03 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/H-magicWands.vpr @@ -0,0 +1,155 @@ +field next : Ref +field val : Int +field f: 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("Explicit") + inhale x > 0 + @dependency("Explicit") + inhale x > 0 --* y > 0 + + // $PrecisionTest: $READ_ONLY=x,y $INVARIANT=x>0 + + @dependency("Rewrite") + apply x > 0 --* y > 0 + + @testAssertion("Explicit") + assert y > 0 +} + + +method basicPackage(l: Ref) + requires @dependency("Precondition")(list(l)) + ensures list(l) + { + @dependency("Rewrite") + unfold list(l) + var tmp : Ref + @dependency("SourceCode") + tmp := l.next + + // $PrecisionTest: $READ_WRITE=l.val $ACC_INVARIANT=acc(l.val)&&acc(l.next, 1/2) + + @dependency("Rewrite") + package list(tmp) --* list(l) + { + @dependency() + fold list(l) + } + + @dependency("Rewrite") + apply list(tmp) --* list(l) + + @testAssertion("Explicit") + assert list(l) +} + + +predicate greater0(a: Int){ + a > 0 +} + +method advancedPackage(a: Int, b: Int) + { + + @dependency("Rewrite") + package (greater0(a) && greater0(b)) --* greater0(a + b) + { + @dependency("Rewrite") + unfold greater0(a) + @dependency("Rewrite") + unfold greater0(b) + @dependency("Rewrite") + fold greater0(a + b) + } + + @dependency("Explicit") + inhale greater0(a) + @dependency("Explicit") + inhale greater0(b) + + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=(unfolding$_$greater0(b)$_$in$_$b>0) $ACC_INVARIANT=greater0(b) + + @dependency("Rewrite") + apply (greater0(a) && greater0(b)) --* greater0(a + b) + + @testAssertion("Explicit") + assert greater0(a + b) +} + +method quantifiedWand(xs: Seq[Int], b: Int) + 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]) + @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 + + @testAssertion("Explicit") + assert greater0(xs[0] + b) +} + +method packagePrecision(l: Ref) + requires @dependency("Precondition")(list(l)) + { + @dependency("Rewrite") + unfold list(l) + var tmp : Ref + @dependency("SourceCode") + tmp := l.next + + // $PrecisionTest: $READ_WRITE=l.val $ACC_INVARIANT=acc(l.val) + + @irrelevant("Rewrite") + package list(tmp) --* list(l) + { + @irrelevant("Rewrite") + fold list(l) + } + + @testAssertion("Explicit") + assert list(tmp) +} + +method packageExhale(x: Ref) + requires @dependency("Precondition")(acc(x.f)) + { + + @dependency("Rewrite") + package acc(x.f, 1/2) --* acc(x.f) + + // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=acc(x.f,1/4) + + + @testAssertion("Explicit") + 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/I-branches.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/I-branches.vpr new file mode 100644 index 000000000..0706a9ecf --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/I-branches.vpr @@ -0,0 +1,85 @@ +field f: Int + +method branch1(){ + var x: Int, y: Int + + if(@dependency("PathCondition")(x > 0)){ + @dependency("SourceCode") + y := x + 1 + }else{ + @dependency("SourceCode") + y := -x + 1 + + // $PrecisionTest: $READ_WRITE=x $READ_ONLY=y $INVARIANT=y>-1 + } + + @testAssertion("Explicit") + assert y > 0 +} + +method branch2(){ + var x: Int, y: Int + + if(@irrelevant("PathCondition")(x > 0)){ + @dependency("Explicit") + assume y > 0 + }else{ + @dependency("Explicit") + assume y >= 10 + } + + // $PrecisionTest: $READ_WRITE=x $READ_ONLY=y $INVARIANT=y>-1 + + @testAssertion("Explicit") + assert y > 0 +} + +method branch3(){ + var x: Int, y: Int + + // $PrecisionTest: $READ_WRITE=y $READ_ONLY=x + + if(@dependency("PathCondition")(x > 10)){ + @dependency("SourceCode") + y := x + 1 + }else{ + @irrelevant("SourceCode") + y := 10 + @irrelevant("Explicit") + assume y > 0 + } + + @testAssertion("Explicit") + assert x > 10 ==> y > 0 +} + +method branch4(){ + var x: Int, y: Int + + if(@dependency("PathCondition")(x > 0)){ + @irrelevant("SourceCode") + y := x + 1 + // $PrecisionTest: $READ_WRITE=y $READ_ONLY=x $INVARIANT=x>0 + }else{ + @dependency("SourceCode") + y := 10 - x + @testAssertion("Explicit") + assert y > 0 + } +} + +method branch5(){ + var x: Int, y: Int + @dependency("Explicit") + assume y > 0 + + // $PrecisionTest: $READ_ONLY=x,y $INVARIANT=y>0 + + if(@irrelevant("PathCondition")(x > 0)){ + @irrelevant("SourceCode") + y := x + 1 + }else{ + @testAssertion("Explicit") + assert y >= 0 + } +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/J-loops.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/J-loops.vpr new file mode 100644 index 000000000..d91f941b6 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/J-loops.vpr @@ -0,0 +1,96 @@ +field f:Int + +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 + } + + // $PrecisionTest: $READ_ONLY=res $READ_WRITE=i $INVARIANT=i>-1 + + @testAssertion("Explicit") + assert res >= 0 +} + +method loop2(){ + var i: Int + var res: Int + @irrelevant("SourceCode") + res := 0 + @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("SourceCode") + res := res + i + // $PrecisionTest: $READ_ONLY=res,i $INVARIANT=i<20 + @dependency("SourceCode") + i := i - 1 + @testAssertion("Explicit") + assert i >= 0 + } +} + +method loop3(){ + var i: Int + var res: Int + @dependency("SourceCode") + res := 0 + // $PrecisionTest: $READ_ONLY=res $READ_WRITE=i $INVARIANT=res<10 + @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 loop4(){ + var i: Int + var res: Int + + // $PrecisionTest: $READ_WRITE=i,res + + @dependency("SourceCode") + res := 0 + @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("SourceCode") + res := res - 1 + @irrelevant("SourceCode") + i := i - 1 + @dependency("Explicit") + assume res > 0 + } + + @testAssertion("Explicit") + assert res >= 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/K-domain.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/K-domain.vpr new file mode 100644 index 000000000..a3aaa9cd8 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/K-domain.vpr @@ -0,0 +1,74 @@ +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 domain1() +{ + var a: IArray + @irrelevant("Explicit") + inhale len(a) == 3 + + // $PrecisionTest: $INVARIANT=len(a)>0 + + @testAssertion("Implicit") + inhale access(a) +} + +method domain2() +{ + var a: IArray + @dependency("Explicit") + inhale len(a) == 3 + + @dependency("Explicit") + inhale access(a) + + // $PrecisionTest: $READ_WRITE=slot(a;0).val $INVARIANT=0 0) + requires @dependency("Precondition")(acc(a.f)) + requires @irrelevant("Precondition")(a.f > 0) +{ + var res: Int + + // $PrecisionTest: $READ_WRITE=a.f,res $READ_ONLY=n $INVARIANT=n>0 $ACC_INVARIANT=acc(a.f) + + @testAssertion("Implicit") + res := a.f / n +} + +method assumeFalse() +{ + var a: Int + @dependency("Explicit") + assume false + + // $PrecisionTest: $READ_WRITE=a $INVARIANT=a>0 + + @testAssertion("Explicit") + assert a == 2 +} \ No newline at end of file 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..a533a482c --- /dev/null +++ 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) + } + } +} 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 +} 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/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 new file mode 100644 index 000000000..d5b9067e5 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8a.vpr @@ -0,0 +1,23 @@ + +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..002363ad5 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8b.vpr @@ -0,0 +1,24 @@ + +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..b282ef3f3 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8c.vpr @@ -0,0 +1,24 @@ + +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 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/permMax-2.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/permMax-2.vpr new file mode 100644 index 000000000..081f5c3fb --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/permMax-2.vpr @@ -0,0 +1,14 @@ +// Verification Progress: 1.0 + +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/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/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 new file mode 100644 index 000000000..40ba8dbb6 --- /dev/null +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -0,0 +1,245 @@ +package viper.silicon.tests + +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 +import viper.silver.verifier.VerificationResult +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" + val dependencyKeyword = "dependency" + val testAssertionKeyword = "testAssertion" + val EXPORT_PRUNED_PROGRAMS = false + + 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") + + 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(additionalArguments: Seq[String] = Seq.empty): Unit = { + frontend.verifier.stop() + frontend = createFrontend(analysisCommandLineArguments ++ additionalArguments) + } + + var baselineFrontend: SiliconFrontend = createFrontend(baseCommandLineArguments) + + def resetBaselineFrontend(): Unit = { + baselineFrontend.verifier.stop() + baselineFrontend = createFrontend(baseCommandLineArguments) + } + + /** + * (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. + */ + 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) + var id: Int = 0 + // TODO ake: it would be better to work with position string instead of line numbers + 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 = { + val relevantNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) + + val dependencies = fullGraphInterpreter.getAllNonInternalDependencies(relevantNodes.map(_.id)) + + 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()}") + } + + 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 dependency 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 + * + */ + 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) + + var errorMsgs = stmtsWithAssumptionAnnotation.map(checkAssumptionNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq + errorMsgs ++= dependencyGraphInterpreters flatMap checkTestAssertionNodeExists + errorMsgs ++= dependencyGraphInterpreters flatMap checkAllDependencies + errorMsgs ++= dependencyGraphInterpreters 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[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) + 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 or wrong assumption type at line $pos: ${node.toString.replaceAll("\n", " ")}") + } + + protected def extractSourceLine(pos: ast.Position): Int = { + pos match { + case column: ast.HasLineColumn => column.line + case _ => -1 + } + } + + 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) + Seq(s"Missing testAssertion for ${dependencyGraphInterpreter.getName}") + else + Seq.empty + } + + + protected def checkAllDependencies(dependencyGraphInterpreter: DependencyGraphInterpreter[IntraProcedural]): Seq[String] = { + val assertionNodes = getTestAssertionNodes(dependencyGraphInterpreter.getNonInternalAssertionNodes) + 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") + + val resIrrelevant = if(checkPrecision){ + val irrelevantNodes = getTestIrrelevantAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) + checkDependenciesAndGetErrorMsgs(irrelevantNodes, dependencies, isDependencyExpected = false, "Unexpected dependency") + } else Seq.empty + + resRelevant ++ resIrrelevant + } + + protected def checkExplicitDependencies(dependencyGraphInterpreter: DependencyGraphInterpreter[IntraProcedural]): Seq[String] = { + val assertionNodes = getTestAssertionNodes(dependencyGraphInterpreter.getNonInternalAssertionNodes) + val dependencies = dependencyGraphInterpreter.getAllExplicitDependencies(assertionNodes.map(_.id)).map(_.id) + + 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") + + 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[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 + Option.when(!(isDependencyExpected == hasDependency))(s"$errorMsg: ${assumptions.head.sourceInfo.toString}") + }).filter(_.isDefined).map(_.get).toSeq + resRelevant + } + + protected def getTestAssertionNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = + nodes.filter(node => node.sourceInfo.toString.contains("@" + testAssertionKeyword + "(")) + + + protected def getTestAssumptionNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = + nodes.filter(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(")) + + + 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 new file mode 100644 index 000000000..84db1e21e --- /dev/null +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -0,0 +1,59 @@ +package viper.silicon.tests + +import org.scalatest.funsuite.AnyFunSuite +import viper.silicon.dependencyAnalysis._ +import viper.silver.ast._ +import viper.silver.frontend.SilFrontend +import viper.silver.verifier + + +class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFramework { + + val CHECK_PRECISION = false + val EXECUTE_TEST = true + override val EXPORT_PRUNED_PROGRAMS: Boolean = false + val ignores: Seq[String] = Seq("iterativeTreeDelete") + analysisCommandLineArguments = analysisCommandLineArguments ++ Seq("--executeDependencyAnalysisTests") + val testDirectories: Seq[String] = Seq( + "dependencyAnalysisTests/all", + "dependencyAnalysisTests/unitTests", + "dependencyAnalysisTests/real-world-examples", + ) + + 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{ + resetFrontend() + executeTest(dirName + "/", fileName, frontend) + }catch{ + case t: Throwable => fail(t) + } + } + } + + def executeTest(filePrefix: String, + fileName: String, + frontend: SilFrontend): Unit = { + + 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 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() + } +} diff --git a/src/test/scala/VerificationProgressRunner.scala b/src/test/scala/VerificationProgressRunner.scala new file mode 100644 index 000000000..0b646e77d --- /dev/null +++ b/src/test/scala/VerificationProgressRunner.scala @@ -0,0 +1,69 @@ +package viper.silicon.tests + +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 +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", + "dependencyAnalysisTests/verificationProgress/guidanceTest", + "dependencyAnalysisTests/verificationProgress/perms" + ) + + 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\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)) { + 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 result: VerificationResult = frontend.verifier.verify(program) + + val hasFailures = result.isInstanceOf[verifier.Failure] + + val joinedDependencyGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter + + 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}") + } +}