From 9c034cc7a32a798704513c7fdda03074c44c7ef0 Mon Sep 17 00:00:00 2001 From: Dina Weiersmueller Date: Wed, 9 Nov 2022 15:58:48 +0100 Subject: [PATCH 01/41] Existential Elimination v1 --- src/main/scala/viper/silver/ast/Program.scala | 3 +- .../viper/silver/frontend/SilFrontend.scala | 4 +- .../scala/viper/silver/parser/Resolver.scala | 4 + .../reasoning/ReasoningASTExtension.scala | 42 +++++++++ .../standard/reasoning/ReasoningErrors.scala | 20 +++++ .../reasoning/ReasoningPASTExtension.scala | 60 +++++++++++++ .../standard/reasoning/ReasoningPlugin.scala | 90 +++++++++++++++++++ .../reasoning/existential_elim_simple.vpr | 8 ++ .../reasoning/existential_elim_trigger.vpr | 13 +++ 9 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala create mode 100644 src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala create mode 100644 src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala create mode 100644 src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala create mode 100644 src/test/resources/reasoning/existential_elim_simple.vpr create mode 100644 src/test/resources/reasoning/existential_elim_trigger.vpr diff --git a/src/main/scala/viper/silver/ast/Program.scala b/src/main/scala/viper/silver/ast/Program.scala index 04e3e9295..da5e53902 100644 --- a/src/main/scala/viper/silver/ast/Program.scala +++ b/src/main/scala/viper/silver/ast/Program.scala @@ -184,7 +184,8 @@ case class Program(domains: Seq[Domain], fields: Seq[Field], functions: Seq[Func declarationMap.get(name) match { case Some(d: LocalVarDecl) => if(d.typ == n.typ) None else Some(ConsistencyError(s"No matching local variable $name found with type ${n.typ}, instead found ${d.typ}.", n.pos)) case Some(d) => Some(ConsistencyError(s"No matching local variable $name found with type ${n.typ}, instead found other identifier of type ${d.getClass.getSimpleName}.", n.pos)) - case None => Some(ConsistencyError(s"Local variable $name not found.", n.pos)) + case None => scala.Console.println("in checkLocalVarUse declarationMap = " + declarationMap.mkString) + Some(ConsistencyError(s"Local variable $name not found.", n.pos)) } } def checkNameUse[T](name: String, n: Positioned, expected: String, declarationMap: immutable.HashMap[String, Declaration])(implicit tag: ClassTag[T]) : Option[ConsistencyError] = { diff --git a/src/main/scala/viper/silver/frontend/SilFrontend.scala b/src/main/scala/viper/silver/frontend/SilFrontend.scala index b3a5e1128..53a6e3e9e 100644 --- a/src/main/scala/viper/silver/frontend/SilFrontend.scala +++ b/src/main/scala/viper/silver/frontend/SilFrontend.scala @@ -84,7 +84,9 @@ trait SilFrontend extends DefaultFrontend { "viper.silver.plugin.standard.adt.AdtPlugin", "viper.silver.plugin.standard.termination.TerminationPlugin", "viper.silver.plugin.standard.refute.RefutePlugin", - "viper.silver.plugin.standard.predicateinstance.PredicateInstancePlugin" + "viper.silver.plugin.standard.predicateinstance.PredicateInstancePlugin", + "viper.silver.plugin.standard.reasoning.ReasoningPlugin" + ) /** For testing of plugin import feature */ def defaultPluginCount: Int = defaultPlugins.size diff --git a/src/main/scala/viper/silver/parser/Resolver.scala b/src/main/scala/viper/silver/parser/Resolver.scala index fd1461a18..8f66566f8 100644 --- a/src/main/scala/viper/silver/parser/Resolver.scala +++ b/src/main/scala/viper/silver/parser/Resolver.scala @@ -868,6 +868,9 @@ case class NameAnalyser() { case Some(_: PErrorEntity) => case None => getMap(d).put(d.idndef.name, d) + scala.Console.println("Line 871: " + d.idndef.name) + scala.Console.println("getMap(d) = " + getMap(d).mkString) + } } case _ => @@ -984,6 +987,7 @@ case class NameAnalyser() { } def run(p: PProgram): Boolean = { + scala.Console.println("in Resolver line 988") check(p, None) messages.isEmpty || messages.forall(m => !m.error) } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala new file mode 100644 index 000000000..2040c75bf --- /dev/null +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala @@ -0,0 +1,42 @@ +package viper.silver.plugin.standard.reasoning + +import viper.silver.ast._ +import viper.silver.ast.pretty.FastPrettyPrinter.{ContOps, char, group, showStmt, showType, showVars, ssep, text, toParenDoc} +import viper.silver.ast.pretty.PrettyPrintPrimitives + +/** An `FailureExpectedInfo` info that tells us that this assert is a existential elimination. */ +case object ReasoningInfo extends FailureExpectedInfo + +//version with Local var decl +case class ExistentialElim(varList: Seq[LocalVarDecl], exp: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt { + +// version without local var decl (not used even in version without local vars in PAST) +//case class ExistentialElim(varList: Seq[(String, Type)], exp: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt { + + override lazy val prettyPrint: PrettyPrintPrimitives#Cont = { + //version with local var decl + text("obtain") <+> showVars(varList) <+> + text("where") <+> toParenDoc(exp) + + // version without local var decl (not used even in version without local vars in PAST) + //text("obtain") <+> text("(") <+> ssep(varList.map { case (id,typ) => text(id) <+> ":" <+> showType(typ)}, group(char (','))) <+> text(")") <+> + //text("where") <+> toParenDoc(exp) + } + + // version with local var decl + //override val extensionSubnodes: Seq[Node] = varList ++ Seq(exp) + + // version without local var decl + override val extensionSubnodes: Seq[Node] = Seq(exp) + +} +/* +case class ExistentialElim(vardecl:Arg, exp: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt { + override lazy val prettyPrint: PrettyPrintPrimitives#Cont = { + text("obtain") <+> showVars(Seq(vardecl)) + text("where") <+> toParenDoc(exp) + } + + override val extensionSubnodes: Seq[Node] = Seq(exp) +} +*/ \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala new file mode 100644 index 000000000..316794fd1 --- /dev/null +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala @@ -0,0 +1,20 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2022 ETH Zurich. + +package viper.silver.plugin.standard.reasoning + +import viper.silver.verifier._ +import viper.silver.verifier.reasons.ErrorNode + +case class ExistentialElimFailed(override val offendingNode: ErrorNode, override val reason: ErrorReason, override val cached: Boolean = false) extends AbstractVerificationError { + override val id = "existential elimination.failed" + override val text = " no witness could be found." + + override def withNode(offendingNode: errors.ErrorNode = this.offendingNode): ExistentialElimFailed = + ExistentialElimFailed(this.offendingNode, this.reason, this.cached) + + override def withReason(r: ErrorReason): ExistentialElimFailed = ExistentialElimFailed(offendingNode, r, cached) +} diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala new file mode 100644 index 000000000..e92928b34 --- /dev/null +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -0,0 +1,60 @@ +package viper.silver.plugin.standard.reasoning + +import viper.silver.ast.{LocalVarDecl, Position, Stmt} +import viper.silver.parser.TypeHelper.Bool +import viper.silver.parser.{NameAnalyser, PExp, PExtender, PLocalVarDecl, PNode, PStmt, PTrigger, PType, Translator, TypeChecker} + +//case class PExistentialElim(varList: Seq[(PIdnDef, PType)], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { + +// version without local var decl in out obtain statement +case class PExistentialElim(varList: Seq[(String, PType)], t: Seq[PTrigger], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { + override val getSubnodes: Seq[PNode] = { + Seq(e) + } + + override def typecheck(t: TypeChecker, n: NameAnalyser): Option[Seq[String]] = { + varList foreach (v => scala.Console.println("local var name = " + v._1)) + t.check(e, Bool) + None + } + + override def translateStmt(t: Translator): Stmt = { + scala.Console.println("entered translateStmt!") + ExistentialElim(varList.map{case (id, typ) => LocalVarDecl(id, t.ttyp(typ))(t.liftPos(e))}, t.exp(e))(t.liftPos(e)) + } +} + + +// version with local var decl in our obtain statement +/* +case class PExistentialElim(varList: Seq[PLocalVarDecl], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { + override val getSubnodes: Seq[PNode] = { + //varList ++ Seq(e) + varList ++ Seq(e) + } + + override def typecheck(t: TypeChecker, n: NameAnalyser): Option[Seq[String]] = { + varList foreach (v => scala.Console.println("local var name = " + v.idndef.name)) + varList foreach { + //v => n.namesInScope(v, Some(this)) + v => t.check(v) + } + t.check(e, Bool) + None + } + + override def translateStmt(t: Translator): Stmt = { + scala.Console.println("entered translateStmt!") + ExistentialElim(varList.map { case variable => LocalVarDecl(variable.idndef.name, t.ttyp(variable.typ))(t.liftPos(variable)) }, t.exp(e))(t.liftPos(e)) + } + + //override def translateStmt(t: Translator): Stmt = ExistentialElim(varList.map{ case (id, typ) => LocalVarDecl(id.name, t.ttyp(typ))(t.liftPos(id))} ,t.exp(e))(t.liftPos(this)) + //LocalVarDecl(varList.name, t.ttyp(typ))(t.liftPos(id)) +} + */ +/* +case class PExistentialElim(lvd:PFormalArgDecl, e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { + override val getSubnodes: Seq[PNode] = Seq(e) + +} +*/ \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala new file mode 100644 index 000000000..7e49fdfd7 --- /dev/null +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -0,0 +1,90 @@ +package viper.silver.plugin.standard.reasoning + + +import fastparse.P +import viper.silver.ast._ +import viper.silver.ast.utility.ViperStrategy +import viper.silver.ast.utility.rewriter.Traverse +import viper.silver.parser.FastParserCompanion.whitespace +import viper.silver.parser.{FastParser, PLocalVarDecl} +import viper.silver.plugin.{ParserPluginTemplate, SilverPlugin} +import viper.silver.verifier.{AbstractError, Failure, Success, VerificationResult} +import viper.silver.verifier.errors.AssertFailed + +import scala.annotation.unused + +class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, + @unused logger: ch.qos.logback.classic.Logger, + @unused config: viper.silver.frontend.SilFrontendConfig, + fp: FastParser) extends SilverPlugin with ParserPluginTemplate { + + import fp.{FP, keyword, exp, ParserExtension, idndef, typ, trigger} + + /** Parser for existential elimination statements. */ + def existential_elim[_: P]: P[PExistentialElim] = { + //FP(keyword("obtain") ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ keyword("where") ~/ exp).map{ case (pos, (varList, e)) => PExistentialElim(varList, e)(pos) } + // version with local var decl + //FP(keyword("obtain") ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ keyword("where") ~/ exp).map{ case (pos, (varList, e)) => PExistentialElim(varList.map { case (id,typ) => PLocalVarDecl(id,typ,None)(e.pos)}, e)(pos) } + + //version without local var decl and with triggers + FP(keyword("obtain") ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ keyword("where") ~/ trigger.rep ~/ exp).map { case (pos, (varList, t, e)) => PExistentialElim(varList.map { case (id, typ) => (id.name, typ) }, t, e)(pos) } + + } + // trigger after where (Seq(PTrigger)) + + + /** Add existential elimination to the parser. */ + override def beforeParse(input: String, isImported: Boolean): String = { + scala.Console.println("entered beforeParse: " + input) + // Add new keyword + ParserExtension.addNewKeywords(Set[String]("obtain", "where")) + // Add new parser to the precondition + ParserExtension.addNewStmtAtEnd(existential_elim(_)) + input + } + + + override def beforeVerify(input: Program): Program = { + scala.Console.println("entered beforeVerify: "+ input.info.toString) + ViperStrategy.Slim({ + case e@ExistentialElim(v, exp) => { // e = ExistentialElim(vardecl, exp) + Seqn( + Seq( + // version with local var decl + Assert(Exists(v, Seq(), exp)(e.pos, ReasoningInfo))(e.pos)) + + // version without local var decl + //Assert(Exists(v.map{case (id,typ) => LocalVarDecl(id,typ)(exp.pos)}, Seq(), exp)(e.pos, ReasoningInfo))(e.pos)) + ++ + // version with local var decl + v.map { case variable => LocalVarDeclStmt(variable)(variable.pos) } //list of variables + + // version without local var decl + //v.map { case variable => LocalVarDeclStmt(LocalVarDecl(variable._1,variable._2)(exp.pos))(exp.pos) } //list of variables + + ++ + Seq( + Inhale(exp)(e.pos) + ), + Seq() + )(e.pos) + } + }, Traverse.TopDown).execute(input) + } + + override def mapVerificationResult(input: VerificationResult): VerificationResult = { + val errors: Seq[AbstractError] = (input match { + case Success => Seq() + case Failure(errors) => { + errors.map { + case AssertFailed(a, err_reason, c) if a.info == ReasoningInfo => { + ExistentialElimFailed(a, err_reason, c) + } + } + } + }) + if (errors.length == 0) Success + else Failure(errors) + } +} + diff --git a/src/test/resources/reasoning/existential_elim_simple.vpr b/src/test/resources/reasoning/existential_elim_simple.vpr new file mode 100644 index 000000000..52edd0609 --- /dev/null +++ b/src/test/resources/reasoning/existential_elim_simple.vpr @@ -0,0 +1,8 @@ +method ex1() +{ + var x :Int + obtain x:Int, y:Int where 2==2 + x:=2 + + +} \ No newline at end of file diff --git a/src/test/resources/reasoning/existential_elim_trigger.vpr b/src/test/resources/reasoning/existential_elim_trigger.vpr new file mode 100644 index 000000000..f866ec781 --- /dev/null +++ b/src/test/resources/reasoning/existential_elim_trigger.vpr @@ -0,0 +1,13 @@ +function geq(x:Int, y:Int) : Bool +{ + x>=y +} + +method ex1() +{ + var x: Int + var y: Int + assert geq(3, 0) + obtain x:Int, y:Int where {geq(x,y)} geq(x,y) + assert x>=y +} \ No newline at end of file From a2c5c6db88676810aa114fa7d061b81d4c3dede1 Mon Sep 17 00:00:00 2001 From: Dina Weiersmueller Date: Wed, 9 Nov 2022 17:19:17 +0100 Subject: [PATCH 02/41] Existential Elimination added Trigger typecheck --- .../reasoning/ReasoningASTExtension.scala | 7 ++++--- .../reasoning/ReasoningPASTExtension.scala | 18 ++++++++++++------ .../standard/reasoning/ReasoningPlugin.scala | 4 ++-- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala index 2040c75bf..7bb41eb21 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala @@ -1,14 +1,14 @@ package viper.silver.plugin.standard.reasoning import viper.silver.ast._ -import viper.silver.ast.pretty.FastPrettyPrinter.{ContOps, char, group, showStmt, showType, showVars, ssep, text, toParenDoc} +import viper.silver.ast.pretty.FastPrettyPrinter.{ContOps, char, group, nil, show, showType, showVars, space, ssep, text, toParenDoc} import viper.silver.ast.pretty.PrettyPrintPrimitives /** An `FailureExpectedInfo` info that tells us that this assert is a existential elimination. */ case object ReasoningInfo extends FailureExpectedInfo //version with Local var decl -case class ExistentialElim(varList: Seq[LocalVarDecl], exp: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt { +case class ExistentialElim(varList: Seq[LocalVarDecl], trigs: Seq[Trigger], exp: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt { // version without local var decl (not used even in version without local vars in PAST) //case class ExistentialElim(varList: Seq[(String, Type)], exp: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt { @@ -16,7 +16,8 @@ case class ExistentialElim(varList: Seq[LocalVarDecl], exp: Exp)(val pos: Positi override lazy val prettyPrint: PrettyPrintPrimitives#Cont = { //version with local var decl text("obtain") <+> showVars(varList) <+> - text("where") <+> toParenDoc(exp) + text("where") <+> (if (trigs.isEmpty) nil else space <> ssep(trigs map show, space)) <+> + toParenDoc(exp) // version without local var decl (not used even in version without local vars in PAST) //text("obtain") <+> text("(") <+> ssep(varList.map { case (id,typ) => text(id) <+> ":" <+> showType(typ)}, group(char (','))) <+> text(")") <+> diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala index e92928b34..68cf5ea16 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -1,26 +1,32 @@ package viper.silver.plugin.standard.reasoning -import viper.silver.ast.{LocalVarDecl, Position, Stmt} +import viper.silver.ast.{LocalVarDecl, Position, Stmt, Trigger} import viper.silver.parser.TypeHelper.Bool -import viper.silver.parser.{NameAnalyser, PExp, PExtender, PLocalVarDecl, PNode, PStmt, PTrigger, PType, Translator, TypeChecker} +import viper.silver.parser.{NameAnalyser, PExp, PExtender, PIdnDef, PLocalVarDecl, PNode, PStmt, PTrigger, PType, Translator, TypeChecker} //case class PExistentialElim(varList: Seq[(PIdnDef, PType)], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { // version without local var decl in out obtain statement -case class PExistentialElim(varList: Seq[(String, PType)], t: Seq[PTrigger], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { +case class PExistentialElim(varList: Seq[(String, PType)], trig: Seq[PTrigger], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { override val getSubnodes: Seq[PNode] = { - Seq(e) + trig ++ Seq(e) } override def typecheck(t: TypeChecker, n: NameAnalyser): Option[Seq[String]] = { - varList foreach (v => scala.Console.println("local var name = " + v._1)) + varList foreach (v => { + scala.Console.println("local var name = " + v._1) + t.check(PLocalVarDecl(PIdnDef(v._1)(e.pos),v._2, None)(e.pos)) + }) + trig.foreach (trigger => trigger.exp.map{ trigexpr => t.check(trigexpr,Bool)}) + + t.check(e, Bool) None } override def translateStmt(t: Translator): Stmt = { scala.Console.println("entered translateStmt!") - ExistentialElim(varList.map{case (id, typ) => LocalVarDecl(id, t.ttyp(typ))(t.liftPos(e))}, t.exp(e))(t.liftPos(e)) + ExistentialElim(varList.map{case (id, typ) => LocalVarDecl(id, t.ttyp(typ))(t.liftPos(e))}, trig.map{case t1 => Trigger(t1.exp.map{ t2 => t.exp(t2)})(t.liftPos(e))}, t.exp(e))(t.liftPos(e)) } } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index 7e49fdfd7..b406d7701 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -47,11 +47,11 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, override def beforeVerify(input: Program): Program = { scala.Console.println("entered beforeVerify: "+ input.info.toString) ViperStrategy.Slim({ - case e@ExistentialElim(v, exp) => { // e = ExistentialElim(vardecl, exp) + case e@ExistentialElim(v, trigs, exp) => { // e = ExistentialElim(vardecl, exp) Seqn( Seq( // version with local var decl - Assert(Exists(v, Seq(), exp)(e.pos, ReasoningInfo))(e.pos)) + Assert(Exists(v, trigs, exp)(e.pos, ReasoningInfo))(e.pos)) // version without local var decl //Assert(Exists(v.map{case (id,typ) => LocalVarDecl(id,typ)(exp.pos)}, Seq(), exp)(e.pos, ReasoningInfo))(e.pos)) From 1b5618763715e0c20e04230014c59b4882360ee8 Mon Sep 17 00:00:00 2001 From: Dina Weiersmueller Date: Wed, 9 Nov 2022 17:29:27 +0100 Subject: [PATCH 03/41] Existential Elimination comments --- .../standard/reasoning/ReasoningASTExtension.scala | 12 +----------- .../reasoning/ReasoningPASTExtension.scala | 14 ++++++++++---- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala index 7bb41eb21..7e865d1e9 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala @@ -30,14 +30,4 @@ case class ExistentialElim(varList: Seq[LocalVarDecl], trigs: Seq[Trigger], exp: // version without local var decl override val extensionSubnodes: Seq[Node] = Seq(exp) -} -/* -case class ExistentialElim(vardecl:Arg, exp: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt { - override lazy val prettyPrint: PrettyPrintPrimitives#Cont = { - text("obtain") <+> showVars(Seq(vardecl)) - text("where") <+> toParenDoc(exp) - } - - override val extensionSubnodes: Seq[Node] = Seq(exp) -} -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala index 68cf5ea16..aaec005ba 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -7,6 +7,7 @@ import viper.silver.parser.{NameAnalyser, PExp, PExtender, PIdnDef, PLocalVarDec //case class PExistentialElim(varList: Seq[(PIdnDef, PType)], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { // version without local var decl in out obtain statement + case class PExistentialElim(varList: Seq[(String, PType)], trig: Seq[PTrigger], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { override val getSubnodes: Seq[PNode] = { trig ++ Seq(e) @@ -51,13 +52,18 @@ case class PExistentialElim(varList: Seq[PLocalVarDecl], e: PExp)(val pos: (Posi override def translateStmt(t: Translator): Stmt = { scala.Console.println("entered translateStmt!") - ExistentialElim(varList.map { case variable => LocalVarDecl(variable.idndef.name, t.ttyp(variable.typ))(t.liftPos(variable)) }, t.exp(e))(t.liftPos(e)) + ExistentialElim(varList.map { case variable => LocalVarDecl(variable.idndef.name, t.ttyp(variable.typ))(t.liftPos(variable)) }, Seq(Trigger(Seq())(t.liftPos(e))), t.exp(e))(t.liftPos(e)) } - //override def translateStmt(t: Translator): Stmt = ExistentialElim(varList.map{ case (id, typ) => LocalVarDecl(id.name, t.ttyp(typ))(t.liftPos(id))} ,t.exp(e))(t.liftPos(this)) - //LocalVarDecl(varList.name, t.ttyp(typ))(t.liftPos(id)) } - */ +*/ + + + + + + + /* case class PExistentialElim(lvd:PFormalArgDecl, e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { override val getSubnodes: Seq[PNode] = Seq(e) From 2eba4da958de279fe0edb04ac86920f4ff9ba44b Mon Sep 17 00:00:00 2001 From: Dina Weiersmueller Date: Wed, 16 Nov 2022 16:54:15 +0100 Subject: [PATCH 04/41] Universal Introduction v1 --- src/main/scala/viper/silver/ast/Program.scala | 3 +- .../viper/silver/frontend/SilFrontend.scala | 11 +- .../scala/viper/silver/parser/Resolver.scala | 4 - .../reasoning/ReasoningASTExtension.scala | 58 +++++++--- .../reasoning/ReasoningPASTExtension.scala | 64 +++------- .../standard/reasoning/ReasoningPlugin.scala | 109 +++++++++++++----- .../reasoning/existential_elim_impure.vpr | 9 ++ .../reasoning/existential_elim_simple.vpr | 16 +-- .../reasoning/existential_elim_trigger.vpr | 4 +- .../reasoning/universal_intro_simple.vpr | 7 ++ 10 files changed, 176 insertions(+), 109 deletions(-) create mode 100644 src/test/resources/reasoning/existential_elim_impure.vpr create mode 100644 src/test/resources/reasoning/universal_intro_simple.vpr diff --git a/src/main/scala/viper/silver/ast/Program.scala b/src/main/scala/viper/silver/ast/Program.scala index d18b21db5..25f3c09a7 100644 --- a/src/main/scala/viper/silver/ast/Program.scala +++ b/src/main/scala/viper/silver/ast/Program.scala @@ -184,8 +184,7 @@ case class Program(domains: Seq[Domain], fields: Seq[Field], functions: Seq[Func declarationMap.get(name) match { case Some(d: LocalVarDecl) => if(d.typ == n.typ) None else Some(ConsistencyError(s"No matching local variable $name found with type ${n.typ}, instead found ${d.typ}.", n.pos)) case Some(d) => Some(ConsistencyError(s"No matching local variable $name found with type ${n.typ}, instead found other identifier of type ${d.getClass.getSimpleName}.", n.pos)) - case None => scala.Console.println("in checkLocalVarUse declarationMap = " + declarationMap.mkString) - Some(ConsistencyError(s"Local variable $name not found.", n.pos)) + case None => Some(ConsistencyError(s"Local variable $name not found.", n.pos)) } } def checkNameUse[T](name: String, n: Positioned, expected: String, declarationMap: immutable.HashMap[String, Declaration])(implicit tag: ClassTag[T]) : Option[ConsistencyError] = { diff --git a/src/main/scala/viper/silver/frontend/SilFrontend.scala b/src/main/scala/viper/silver/frontend/SilFrontend.scala index 53a6e3e9e..5d62febbe 100644 --- a/src/main/scala/viper/silver/frontend/SilFrontend.scala +++ b/src/main/scala/viper/silver/frontend/SilFrontend.scala @@ -16,6 +16,7 @@ import viper.silver.verifier._ import fastparse.Parsed import java.nio.file.{Path, Paths} import viper.silver.FastMessaging +import viper.silver.ast.pretty.FastPrettyPrinter /** * Common functionality to implement a command-line verifier for Viper. This trait @@ -321,8 +322,14 @@ trait SilFrontend extends DefaultFrontend { } val errors = input.checkTransitively - if (errors.isEmpty) - filter(input) + if (errors.isEmpty) { + val prog = filter(input) + prog match { + case Succ(p) => println(FastPrettyPrinter.pretty(p)) + case _ => + } + prog + } else Fail(errors) } diff --git a/src/main/scala/viper/silver/parser/Resolver.scala b/src/main/scala/viper/silver/parser/Resolver.scala index 8f66566f8..fd1461a18 100644 --- a/src/main/scala/viper/silver/parser/Resolver.scala +++ b/src/main/scala/viper/silver/parser/Resolver.scala @@ -868,9 +868,6 @@ case class NameAnalyser() { case Some(_: PErrorEntity) => case None => getMap(d).put(d.idndef.name, d) - scala.Console.println("Line 871: " + d.idndef.name) - scala.Console.println("getMap(d) = " + getMap(d).mkString) - } } case _ => @@ -987,7 +984,6 @@ case class NameAnalyser() { } def run(p: PProgram): Boolean = { - scala.Console.println("in Resolver line 988") check(p, None) messages.isEmpty || messages.forall(m => !m.error) } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala index 7e865d1e9..ba5485f0b 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala @@ -1,33 +1,59 @@ package viper.silver.plugin.standard.reasoning import viper.silver.ast._ -import viper.silver.ast.pretty.FastPrettyPrinter.{ContOps, char, group, nil, show, showType, showVars, space, ssep, text, toParenDoc} +import viper.silver.ast.pretty.FastPrettyPrinter.{ContOps, nil, show, showVars, space, ssep, text, toParenDoc} import viper.silver.ast.pretty.PrettyPrintPrimitives +import viper.silver.ast.utility.QuantifiedPermissions.QuantifiedPermissionAssertion +import viper.silver.ast.utility.{Consistency, Expressions} +import viper.silver.ast.utility.rewriter.Traverse +import viper.silver.verifier.ConsistencyError + +import scala.collection.mutable /** An `FailureExpectedInfo` info that tells us that this assert is a existential elimination. */ case object ReasoningInfo extends FailureExpectedInfo -//version with Local var decl case class ExistentialElim(varList: Seq[LocalVarDecl], trigs: Seq[Trigger], exp: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt { - -// version without local var decl (not used even in version without local vars in PAST) -//case class ExistentialElim(varList: Seq[(String, Type)], exp: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt { + override lazy val check: Seq[ConsistencyError] = Consistency.checkPure(exp) ++ + (if (!(exp isSubtype Bool)) Seq(ConsistencyError(s"Body of existential quantifier must be of Bool type, but found ${exp.typ}", exp.pos)) else Seq()) ++ + (if (varList.isEmpty) Seq(ConsistencyError("Quantifier must have at least one quantified variable.", pos)) else Seq()) override lazy val prettyPrint: PrettyPrintPrimitives#Cont = { - //version with local var decl text("obtain") <+> showVars(varList) <+> - text("where") <+> (if (trigs.isEmpty) nil else space <> ssep(trigs map show, space)) <+> - toParenDoc(exp) - - // version without local var decl (not used even in version without local vars in PAST) - //text("obtain") <+> text("(") <+> ssep(varList.map { case (id,typ) => text(id) <+> ":" <+> showType(typ)}, group(char (','))) <+> text(")") <+> - //text("where") <+> toParenDoc(exp) + text("where") <+> (if (trigs.isEmpty) nil else space <> ssep(trigs map show, space)) <+> + toParenDoc(exp) } - // version with local var decl - //override val extensionSubnodes: Seq[Node] = varList ++ Seq(exp) + override val extensionSubnodes: Seq[Node] = varList ++ trigs ++ Seq(exp) + + + /** declarations contributed by this statement that should be added to the parent scope */ + override def declarationsInParentScope: Seq[Declaration] = varList + + +} + +case class UniversalIntro(varList: Seq[LocalVarDecl], triggers: Seq[Trigger], exp1: Exp, exp2: Exp, block: Seqn)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt { + // See also Expression Line 566 + override lazy val check: Seq[ConsistencyError] = + (if (!(exp1 isSubtype Bool)) Seq(ConsistencyError(s"Body of universal quantifier must be of Bool type, but found ${exp1.typ}", exp1.pos)) else Seq()) ++ + (if (varList.isEmpty) Seq(ConsistencyError("Quantifier must have at least one quantified variable.", pos)) else Seq()) ++ + Consistency.checkAllVarsMentionedInTriggers(varList, triggers) + //++ checkNoNestedQuantsForQuantPermissions ++ + //checkQuantifiedPermission + + + + + + override lazy val prettyPrint: PrettyPrintPrimitives#Cont = { + text("prove forall") <+> showVars(varList) <+> + text("requires") <+> + toParenDoc(exp1) <+> + text("ensures") <+> toParenDoc(exp2) + } - // version without local var decl - override val extensionSubnodes: Seq[Node] = Seq(exp) + override val extensionSubnodes: Seq[Node] = varList ++ Seq(exp1) ++ Seq(exp2) + override def declarationsInParentScope: Seq[Declaration] = varList } \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala index aaec005ba..9816469cf 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -1,72 +1,42 @@ package viper.silver.plugin.standard.reasoning -import viper.silver.ast.{LocalVarDecl, Position, Stmt, Trigger} +import viper.silver.ast.{LocalVarDecl, Position, Seqn, Stmt, Trigger} import viper.silver.parser.TypeHelper.Bool -import viper.silver.parser.{NameAnalyser, PExp, PExtender, PIdnDef, PLocalVarDecl, PNode, PStmt, PTrigger, PType, Translator, TypeChecker} +import viper.silver.parser.{NameAnalyser, PExp, PExtender, PLocalVarDecl, PNode, PSeqn, PStmt, PTrigger, Translator, TypeChecker} -//case class PExistentialElim(varList: Seq[(PIdnDef, PType)], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { - -// version without local var decl in out obtain statement - -case class PExistentialElim(varList: Seq[(String, PType)], trig: Seq[PTrigger], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { +case class PExistentialElim(varList: Seq[PLocalVarDecl], trig: Seq[PTrigger], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { override val getSubnodes: Seq[PNode] = { - trig ++ Seq(e) + varList ++ trig ++ Seq(e) } override def typecheck(t: TypeChecker, n: NameAnalyser): Option[Seq[String]] = { varList foreach (v => { - scala.Console.println("local var name = " + v._1) - t.check(PLocalVarDecl(PIdnDef(v._1)(e.pos),v._2, None)(e.pos)) + t.check(v.typ) }) - trig.foreach (trigger => trigger.exp.map{ trigexpr => t.check(trigexpr,Bool)}) - - + trig foreach (_.exp foreach (tpe=>t.checkTopTyped(tpe,None))) t.check(e, Bool) None } override def translateStmt(t: Translator): Stmt = { - scala.Console.println("entered translateStmt!") - ExistentialElim(varList.map{case (id, typ) => LocalVarDecl(id, t.ttyp(typ))(t.liftPos(e))}, trig.map{case t1 => Trigger(t1.exp.map{ t2 => t.exp(t2)})(t.liftPos(e))}, t.exp(e))(t.liftPos(e)) + ExistentialElim(varList.map { case variable => LocalVarDecl(variable.idndef.name, t.ttyp(variable.typ))(t.liftPos(variable)) }, trig.map{case t1 => Trigger(t1.exp.map{ t2 => t.exp(t2)})(t.liftPos(t1))}, t.exp(e))(t.liftPos(e)) } -} - -// version with local var decl in our obtain statement -/* -case class PExistentialElim(varList: Seq[PLocalVarDecl], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { - override val getSubnodes: Seq[PNode] = { - //varList ++ Seq(e) - varList ++ Seq(e) - } +} +case class PUniversalIntro(varList: Seq[PLocalVarDecl], triggers: Seq[PTrigger], e1: PExp, e2: PExp, block: PSeqn)(val pos: (Position, Position)) extends PExtender with PStmt { + override val getSubnodes: Seq[PNode] = varList ++ triggers ++ Seq(e1) ++ Seq(e2) ++ Seq(block) override def typecheck(t: TypeChecker, n: NameAnalyser): Option[Seq[String]] = { - varList foreach (v => scala.Console.println("local var name = " + v.idndef.name)) - varList foreach { - //v => n.namesInScope(v, Some(this)) - v => t.check(v) - } - t.check(e, Bool) + varList foreach (v => t.check(v.typ)) + triggers foreach (_.exp foreach (tpe=>t.checkTopTyped(tpe,None))) + t.check(e1, Bool) + t.check(e2, Bool) + t.check(block) None } override def translateStmt(t: Translator): Stmt = { - scala.Console.println("entered translateStmt!") - ExistentialElim(varList.map { case variable => LocalVarDecl(variable.idndef.name, t.ttyp(variable.typ))(t.liftPos(variable)) }, Seq(Trigger(Seq())(t.liftPos(e))), t.exp(e))(t.liftPos(e)) + UniversalIntro(varList.map { case variable => LocalVarDecl(variable.idndef.name, t.ttyp(variable.typ))(t.liftPos(variable))}, triggers.map{case t1 => Trigger(t1.exp.map{ t2 => t.exp(t2)})(t.liftPos(t1))}, t.exp(e1), t.exp(e2), Seqn(block.ss.map{blstmt => t.stmt(blstmt)}, Seq())(t.liftPos(block)))(t.liftPos(e1)) } -} -*/ - - - - - - - -/* -case class PExistentialElim(lvd:PFormalArgDecl, e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { - override val getSubnodes: Seq[PNode] = Seq(e) - -} -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index b406d7701..dcf0d434d 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -3,7 +3,7 @@ package viper.silver.plugin.standard.reasoning import fastparse.P import viper.silver.ast._ -import viper.silver.ast.utility.ViperStrategy +import viper.silver.ast.utility.{Expressions, ViperStrategy} import viper.silver.ast.utility.rewriter.Traverse import viper.silver.parser.FastParserCompanion.whitespace import viper.silver.parser.{FastParser, PLocalVarDecl} @@ -12,66 +12,120 @@ import viper.silver.verifier.{AbstractError, Failure, Success, VerificationResul import viper.silver.verifier.errors.AssertFailed import scala.annotation.unused +import scala.collection.mutable class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, @unused logger: ch.qos.logback.classic.Logger, @unused config: viper.silver.frontend.SilFrontendConfig, fp: FastParser) extends SilverPlugin with ParserPluginTemplate { - import fp.{FP, keyword, exp, ParserExtension, idndef, typ, trigger} + import fp.{FP, keyword, exp, ParserExtension, idndef, typ, trigger, block} /** Parser for existential elimination statements. */ def existential_elim[_: P]: P[PExistentialElim] = { - //FP(keyword("obtain") ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ keyword("where") ~/ exp).map{ case (pos, (varList, e)) => PExistentialElim(varList, e)(pos) } - // version with local var decl - //FP(keyword("obtain") ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ keyword("where") ~/ exp).map{ case (pos, (varList, e)) => PExistentialElim(varList.map { case (id,typ) => PLocalVarDecl(id,typ,None)(e.pos)}, e)(pos) } - - //version without local var decl and with triggers - FP(keyword("obtain") ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ keyword("where") ~/ trigger.rep ~/ exp).map { case (pos, (varList, t, e)) => PExistentialElim(varList.map { case (id, typ) => (id.name, typ) }, t, e)(pos) } + FP(keyword("obtain") ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ keyword("where") ~/ trigger.rep ~/ exp).map{ case (pos, (varList, t, e)) => PExistentialElim(varList.map { case (id,typ) => PLocalVarDecl(id,typ,None)(e.pos)}, t, e)(pos) } } - // trigger after where (Seq(PTrigger)) + /** Parser for universal introduction statements. */ + def universal_intro[_: P]: P[PUniversalIntro] = { + FP(keyword("prove") ~/ keyword("forall") ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ trigger.rep(sep = ",") ~/ keyword("assuming") ~/ exp ~/ keyword("implies") ~/ exp ~/ block).map { case (pos, (varList, triggers, e1, e2, b)) => PUniversalIntro(varList.map { case (id, typ) => PLocalVarDecl(id, typ, None)(e1.pos) }, triggers, e1, e2, b)(pos) } + + } - /** Add existential elimination to the parser. */ + /** Add existential elimination and universal introduction to the parser. */ override def beforeParse(input: String, isImported: Boolean): String = { - scala.Console.println("entered beforeParse: " + input) // Add new keyword - ParserExtension.addNewKeywords(Set[String]("obtain", "where")) + ParserExtension.addNewKeywords(Set[String]("obtain", "where", "prove", "forall", "assuming", "implies")) // Add new parser to the precondition ParserExtension.addNewStmtAtEnd(existential_elim(_)) + ParserExtension.addNewStmtAtEnd(universal_intro(_)) input } override def beforeVerify(input: Program): Program = { - scala.Console.println("entered beforeVerify: "+ input.info.toString) + val usedNames: mutable.Set[String] = collection.mutable.Set(input.transitiveScopedDecls.map(_.name): _*) + + def uniqueName(name: String): String = { + var i = 1 + var newName = name + while (usedNames.contains(newName)) { + newName = name + i + i += 1 + } + usedNames.add(newName) + newName + } + + def substituteWithFreshVars[E <: Exp](vars: Seq[LocalVarDecl], exp: E): (Seq[(LocalVarDecl, LocalVarDecl)], E) = { + val declMapping = vars.map(oldDecl => + oldDecl -> LocalVarDecl(uniqueName(oldDecl.name), oldDecl.typ)(oldDecl.pos, oldDecl.info, oldDecl.errT)) + val transformedExp = applySubstitution(declMapping, exp) + (declMapping, transformedExp) + } + + def applySubstitution[E <: Exp](mapping: Seq[(LocalVarDecl, LocalVarDecl)], exp: E): E = { + Expressions.instantiateVariables(exp, mapping.map(_._1.localVar), mapping.map(_._2.localVar)) + } + ViperStrategy.Slim({ case e@ExistentialElim(v, trigs, exp) => { // e = ExistentialElim(vardecl, exp) + val (new_v_map,new_exp) = substituteWithFreshVars(v, exp) + val new_trigs = trigs.map{ case t => Trigger(t.exps.map{ case e1 => applySubstitution(new_v_map,e1)})(t.pos)} + Seqn( + Seq( + Assert(Exists(new_v_map.map(_._2), new_trigs, new_exp)(e.pos, ReasoningInfo))(e.pos) + ) + ++ + v.map { case variable => LocalVarDeclStmt(variable)(variable.pos) } //list of variables + ++ + Seq( + Inhale(exp)(e.pos) + ), + Seq() + )(e.pos) + } + + case u@UniversalIntro(v, trigs, exp1, exp2, blk) => { + val boolvar = LocalVarDecl(uniqueName("b"), Bool)(exp1.pos) + + val (new_v_map,new_exp1) = substituteWithFreshVars(v, exp1) + val new_exp2 = applySubstitution(new_v_map, exp2) + val arb_vars = new_v_map.map{case vars => vars._2} + val new_trigs = trigs.map{ case t => Trigger(t.exps.map{ case e1 => applySubstitution(new_v_map,e1)})(t.pos)} + // label should also be in used Names => can also use uniqueName function + val lbl = uniqueName("l") Seqn( Seq( - // version with local var decl - Assert(Exists(v, trigs, exp)(e.pos, ReasoningInfo))(e.pos)) - - // version without local var decl - //Assert(Exists(v.map{case (id,typ) => LocalVarDecl(id,typ)(exp.pos)}, Seq(), exp)(e.pos, ReasoningInfo))(e.pos)) + LocalVarDeclStmt(boolvar)(u.pos) + ) ++ - // version with local var decl - v.map { case variable => LocalVarDeclStmt(variable)(variable.pos) } //list of variables - - // version without local var decl - //v.map { case variable => LocalVarDeclStmt(LocalVarDecl(variable._1,variable._2)(exp.pos))(exp.pos) } //list of variables - + v.map{ case vars => LocalVarDeclStmt(vars)(u.pos)} ++ Seq( - Inhale(exp)(e.pos) + Label(lbl, Seq())(u.pos), + If(boolvar.localVar, + Seqn( + Seq( + Inhale(exp1)(exp1.pos) + ), + Seq())(exp1.pos), + Seqn(Seq(), Seq())(exp1.pos) + + )(exp1.pos), + blk, + Assert(Implies(boolvar.localVar,exp2)(exp2.pos))(exp2.pos), + Inhale(Forall(arb_vars,new_trigs, Implies(LabelledOld(new_exp1,lbl)(exp2.pos), new_exp2)(exp2.pos))(exp2.pos))(exp2.pos) ), Seq() - )(e.pos) + )(exp1.pos) } + }, Traverse.TopDown).execute(input) } + override def mapVerificationResult(input: VerificationResult): VerificationResult = { val errors: Seq[AbstractError] = (input match { case Success => Seq() @@ -86,5 +140,4 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, if (errors.length == 0) Success else Failure(errors) } -} - +} \ No newline at end of file diff --git a/src/test/resources/reasoning/existential_elim_impure.vpr b/src/test/resources/reasoning/existential_elim_impure.vpr new file mode 100644 index 000000000..748df03b4 --- /dev/null +++ b/src/test/resources/reasoning/existential_elim_impure.vpr @@ -0,0 +1,9 @@ + +field f: Int + +method ex2() +{ + obtain x: Ref where {x.f} acc(x.f) + //assert exists x: Ref :: acc(x.f) + //:: ExpectedOutput(consistency.error) +} \ No newline at end of file diff --git a/src/test/resources/reasoning/existential_elim_simple.vpr b/src/test/resources/reasoning/existential_elim_simple.vpr index 52edd0609..fbd5c3bfa 100644 --- a/src/test/resources/reasoning/existential_elim_simple.vpr +++ b/src/test/resources/reasoning/existential_elim_simple.vpr @@ -1,8 +1,10 @@ -method ex1() -{ - var x :Int - obtain x:Int, y:Int where 2==2 - x:=2 - +function eq(x: Int, y: Int): Bool { + x == y +} -} \ No newline at end of file +method ex1() + requires exists x: Int :: { eq(x, 42) } eq(x, 42) +{ + obtain x:Int where eq(x, 42) + assert x == 42 +} diff --git a/src/test/resources/reasoning/existential_elim_trigger.vpr b/src/test/resources/reasoning/existential_elim_trigger.vpr index f866ec781..980e43b76 100644 --- a/src/test/resources/reasoning/existential_elim_trigger.vpr +++ b/src/test/resources/reasoning/existential_elim_trigger.vpr @@ -5,9 +5,7 @@ function geq(x:Int, y:Int) : Bool method ex1() { - var x: Int - var y: Int assert geq(3, 0) obtain x:Int, y:Int where {geq(x,y)} geq(x,y) assert x>=y -} \ No newline at end of file +} diff --git a/src/test/resources/reasoning/universal_intro_simple.vpr b/src/test/resources/reasoning/universal_intro_simple.vpr new file mode 100644 index 000000000..38c893d19 --- /dev/null +++ b/src/test/resources/reasoning/universal_intro_simple.vpr @@ -0,0 +1,7 @@ +method ex1() +{ + var y: Int + prove forall x:Int {x>0} assuming x>0 implies x>1 { + x:=x+1 + } +} \ No newline at end of file From 2b2211f80d67d3c9360af76b42b484c12829a26e Mon Sep 17 00:00:00 2001 From: Dina Weiersmueller Date: Tue, 14 Feb 2023 10:48:56 +0100 Subject: [PATCH 05/41] Added Flow Analysis with Graph --- .../reasoning/ReasoningASTExtension.scala | 86 +- .../standard/reasoning/ReasoningErrors.scala | 20 + .../reasoning/ReasoningPASTExtension.scala | 71 +- .../standard/reasoning/ReasoningPlugin.scala | 357 ++++++- .../reasoning/analysis/VarAnalysisGraph.scala | 986 ++++++++++++++++++ .../reasoning/analysis/VarAnalysisSet.scala | 415 ++++++++ .../reasoning/existential_elim_impure.vpr | 2 +- src/test/resources/reasoning/immutableVar.vpr | 71 ++ .../outsideBlockAssignmentUnivIntro.vpr | 347 ++++++ src/test/resources/reasoning/test.vpr | 330 ++++++ .../universal_intro_assuming_false.vpr | 19 + .../reasoning/universal_intro_simple.vpr | 29 +- 12 files changed, 2671 insertions(+), 62 deletions(-) create mode 100644 src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala create mode 100644 src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala create mode 100644 src/test/resources/reasoning/immutableVar.vpr create mode 100644 src/test/resources/reasoning/outsideBlockAssignmentUnivIntro.vpr create mode 100644 src/test/resources/reasoning/test.vpr create mode 100644 src/test/resources/reasoning/universal_intro_assuming_false.vpr diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala index ba5485f0b..ca990569f 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala @@ -1,14 +1,11 @@ package viper.silver.plugin.standard.reasoning import viper.silver.ast._ -import viper.silver.ast.pretty.FastPrettyPrinter.{ContOps, nil, show, showVars, space, ssep, text, toParenDoc} +import viper.silver.ast.pretty.FastPrettyPrinter.{ContOps, char, group, line, nil, show, showBlock, showVars, space, ssep, text, toParenDoc} import viper.silver.ast.pretty.PrettyPrintPrimitives -import viper.silver.ast.utility.QuantifiedPermissions.QuantifiedPermissionAssertion import viper.silver.ast.utility.{Consistency, Expressions} -import viper.silver.ast.utility.rewriter.Traverse -import viper.silver.verifier.ConsistencyError +import viper.silver.verifier.{ConsistencyError, Failure, VerificationResult} -import scala.collection.mutable /** An `FailureExpectedInfo` info that tells us that this assert is a existential elimination. */ case object ReasoningInfo extends FailureExpectedInfo @@ -33,27 +30,90 @@ case class ExistentialElim(varList: Seq[LocalVarDecl], trigs: Seq[Trigger], exp: } -case class UniversalIntro(varList: Seq[LocalVarDecl], triggers: Seq[Trigger], exp1: Exp, exp2: Exp, block: Seqn)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt { +case class UniversalIntro(varList: Seq[LocalVarDecl], triggers: Seq[Trigger], exp1: Exp, exp2: Exp, block: Seqn)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt with Scope { // See also Expression Line 566 override lazy val check: Seq[ConsistencyError] = (if (!(exp1 isSubtype Bool)) Seq(ConsistencyError(s"Body of universal quantifier must be of Bool type, but found ${exp1.typ}", exp1.pos)) else Seq()) ++ (if (varList.isEmpty) Seq(ConsistencyError("Quantifier must have at least one quantified variable.", pos)) else Seq()) ++ Consistency.checkAllVarsMentionedInTriggers(varList, triggers) - //++ checkNoNestedQuantsForQuantPermissions ++ - //checkQuantifiedPermission - + override val scopedDecls = varList override lazy val prettyPrint: PrettyPrintPrimitives#Cont = { text("prove forall") <+> showVars(varList) <+> - text("requires") <+> + text("assuming") <+> toParenDoc(exp1) <+> - text("ensures") <+> toParenDoc(exp2) + text("implies") <+> toParenDoc(exp2) <+> + showBlock(block) } - override val extensionSubnodes: Seq[Node] = varList ++ Seq(exp1) ++ Seq(exp2) + override val extensionSubnodes: Seq[Node] = varList ++ triggers ++ Seq(exp1) ++ Seq(exp2) ++ Seq(block) +} + - override def declarationsInParentScope: Seq[Declaration] = varList +sealed trait FlowAnnotation extends ExtensionExp with Node with Scope { + override def extensionIsPure: Boolean = true + + override val scopedDecls = Seq() + + override def typ: Type = Bool + + override def verifyExtExp(): VerificationResult = { + assert(assertion = false, "FlowAnalysis: verifyExtExp has not been implemented.") + Failure(Seq(ConsistencyError("FlowAnalysis: verifyExtExp has not been implemented.", pos))) + + } +} + + +case class FlowAnnotationVar(v: Exp, varList: Seq[Exp])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowAnnotation { + + override def extensionSubnodes: Seq[Node] = Seq(v) ++ varList + + /** Pretty printing functionality as defined for other nodes in class FastPrettyPrinter. + * Sample implementation would be text("old") <> parens(show(e)) for pretty-printing an old-expression. */ + override def prettyPrint: PrettyPrintPrimitives#Cont = { + text("influenced") <+> show(v) <+> + text("by") <+> + ssep(varList map show, group(char (',') <> line(" "))) + } +} + +case class FlowAnnotationVarHeapArg(v: Exp, varList: Seq[Exp])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowAnnotation { + + override def extensionSubnodes: Seq[Node] = Seq(v) ++ varList + override def prettyPrint: PrettyPrintPrimitives#Cont = { + text("influenced") <+> show(v) <+> + text("by") <+> + ssep(varList map show, group(char(',') <> line(" "))) <+> text(", heap") + } +} + +case class FlowAnnotationHeap(varList: Seq[Exp])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowAnnotation { + + override def extensionSubnodes: Seq[Node] = varList + + /** Pretty printing functionality as defined for other nodes in class FastPrettyPrinter. + * Sample implementation would be text("old") <> parens(show(e)) for pretty-printing an old-expression. */ + override def prettyPrint: PrettyPrintPrimitives#Cont = { + text("influenced heap") <+> + text("by") <+> + ssep(varList map show, group(char (',') <> line(" "))) + } +} + +case class FlowAnnotationHeapHeapArg(varList: Seq[Exp])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowAnnotation { + + override def extensionSubnodes: Seq[Node] = varList + + /** Pretty printing functionality as defined for other nodes in class FastPrettyPrinter. + * Sample implementation would be text("old") <> parens(show(e)) for pretty-printing an old-expression. */ + override def prettyPrint: PrettyPrintPrimitives#Cont = { + text("influenced heap") <+> + text("by") <+> + ssep(varList map show, group(char (',') <> line(" "))) <+> text(", heap") + + } } \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala index 316794fd1..8d65e40ce 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala @@ -18,3 +18,23 @@ case class ExistentialElimFailed(override val offendingNode: ErrorNode, override override def withReason(r: ErrorReason): ExistentialElimFailed = ExistentialElimFailed(offendingNode, r, cached) } + +case class UniversalIntroFailed(override val offendingNode: ErrorNode, override val reason: ErrorReason, override val cached: Boolean = false) extends AbstractVerificationError { + override val id = "universal introduction.failed" + override val text = " not true for all vars." + + override def withNode(offendingNode: errors.ErrorNode = this.offendingNode): UniversalIntroFailed = + UniversalIntroFailed(this.offendingNode, this.reason, this.cached) + + override def withReason(r: ErrorReason): UniversalIntroFailed = UniversalIntroFailed(offendingNode, r, cached) +} + +case class FlowAnalysisFailed(override val offendingNode: ErrorNode, override val reason: ErrorReason, override val cached: Boolean = false) extends AbstractVerificationError { + override val id = "flow analysis.failed" + override val text = " ." + + override def withNode(offendingNode: errors.ErrorNode = this.offendingNode): FlowAnalysisFailed = + FlowAnalysisFailed(this.offendingNode, this.reason, this.cached) + + override def withReason(r: ErrorReason): FlowAnalysisFailed = FlowAnalysisFailed(offendingNode, r, cached) +} diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala index 9816469cf..855587049 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -1,8 +1,8 @@ package viper.silver.plugin.standard.reasoning -import viper.silver.ast.{LocalVarDecl, Position, Seqn, Stmt, Trigger} +import viper.silver.ast.{ExtensionExp, LocalVarDecl, Position, Seqn, Stmt, Trigger} import viper.silver.parser.TypeHelper.Bool -import viper.silver.parser.{NameAnalyser, PExp, PExtender, PLocalVarDecl, PNode, PSeqn, PStmt, PTrigger, Translator, TypeChecker} +import viper.silver.parser.{NameAnalyser, PExp, PExtender, PLocalVarDecl, PNode, PSeqn, PStmt, PTrigger, PType, PTypeSubstitution, Translator, TypeChecker} case class PExistentialElim(varList: Seq[PLocalVarDecl], trig: Seq[PTrigger], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { override val getSubnodes: Seq[PNode] = { @@ -36,7 +36,72 @@ case class PUniversalIntro(varList: Seq[PLocalVarDecl], triggers: Seq[PTrigger], } override def translateStmt(t: Translator): Stmt = { - UniversalIntro(varList.map { case variable => LocalVarDecl(variable.idndef.name, t.ttyp(variable.typ))(t.liftPos(variable))}, triggers.map{case t1 => Trigger(t1.exp.map{ t2 => t.exp(t2)})(t.liftPos(t1))}, t.exp(e1), t.exp(e2), Seqn(block.ss.map{blstmt => t.stmt(blstmt)}, Seq())(t.liftPos(block)))(t.liftPos(e1)) + val res = UniversalIntro(varList.map { case variable => LocalVarDecl(variable.idndef.name, t.ttyp(variable.typ))(t.liftPos(variable))}, triggers.map{case t1 => Trigger(t1.exp.map{ t2 => t.exp(t2)})(t.liftPos(t1))}, t.exp(e1), t.exp(e2), t.stmt(block).asInstanceOf[Seqn])(t.liftPos(e1)) + res } +} + +sealed trait PFlowAnnotation extends PExtender with PExp { + override def typeSubstitutions: Seq[PTypeSubstitution] = Seq(PTypeSubstitution.id) + + override def forceSubstitution(ts: PTypeSubstitution): Unit = {} + + override val getSubnodes: Seq[PNode] = Seq() +} + +case class PFlowAnnotationVar(v:PExp, varList: Seq[PExp])(val pos: (Position,Position)) extends PFlowAnnotation { + + override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = { + varList.foreach(c => { + t.checkTopTyped(c, None) + }) + t.checkTopTyped(v, None) + None + } + + override def translateExp(t: Translator): ExtensionExp = { + FlowAnnotationVar(t.exp(v), varList.map { case variable => t.exp(variable) })(t.liftPos(this)) + + } +} + +/** specified type of the PFlowAnnotationVar class which includes heap as one of the argument variables */ +case class PFlowAnnotationVarHeapArg(v:PExp, varList: Seq[PExp])(val pos:(Position,Position)) extends PFlowAnnotation { + + override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = { + + varList.foreach(c => { + t.checkTopTyped(c, None) + }) + t.checkTopTyped(v, None) + None + } + override def translateExp(t: Translator): ExtensionExp = FlowAnnotationVarHeapArg(t.exp(v), varList.map { case variable => t.exp(variable) })(t.liftPos(this)) +} + +case class PFlowAnnotationHeap(varList: Seq[PExp])(val pos: (Position,Position)) extends PFlowAnnotation { + + override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = { + varList.foreach(c => t.checkTopTyped(c, None)) + None + } + + override def translateExp(t: Translator): ExtensionExp = { + FlowAnnotationHeap(varList.map { case variable => t.exp(variable) })(t.liftPos(this)) + + } +} + +case class PFlowAnnotationHeapHeapArg(varList: Seq[PExp])(val pos: (Position,Position)) extends PFlowAnnotation { + + override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = { + varList.foreach(c => t.checkTopTyped(c, None)) + None + } + + override def translateExp(t: Translator): ExtensionExp = { + FlowAnnotationHeapHeapArg(varList.map { case variable => t.exp(variable) })(t.liftPos(this)) + + } } \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index dcf0d434d..0dfc57bfc 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -1,45 +1,101 @@ package viper.silver.plugin.standard.reasoning -import fastparse.P +import fastparse._ +import org.jgrapht.Graph +import org.jgrapht.graph.{DefaultDirectedGraph, DefaultEdge} import viper.silver.ast._ -import viper.silver.ast.utility.{Expressions, ViperStrategy} import viper.silver.ast.utility.rewriter.Traverse +import viper.silver.ast.utility.{Expressions, ViperStrategy} import viper.silver.parser.FastParserCompanion.whitespace -import viper.silver.parser.{FastParser, PLocalVarDecl} +import viper.silver.parser._ +import viper.silver.plugin.standard.reasoning.analysis.{VarAnalysisGraph, VarAnalysisSet} import viper.silver.plugin.{ParserPluginTemplate, SilverPlugin} -import viper.silver.verifier.{AbstractError, Failure, Success, VerificationResult} -import viper.silver.verifier.errors.AssertFailed +import viper.silver.verifier._ import scala.annotation.unused import scala.collection.mutable +import scala.jdk.CollectionConverters._ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, @unused logger: ch.qos.logback.classic.Logger, @unused config: viper.silver.frontend.SilFrontendConfig, - fp: FastParser) extends SilverPlugin with ParserPluginTemplate { + fp: FastParser) extends SilverPlugin with ParserPluginTemplate with VarAnalysisSet { + + import fp.{FP, ParserExtension, block, exp, idndef, idnuse, keyword, trigger, typ} - import fp.{FP, keyword, exp, ParserExtension, idndef, typ, trigger, block} + + override def reportErrorWithMsg(error: AbstractError): Unit = reportError(error) /** Parser for existential elimination statements. */ def existential_elim[_: P]: P[PExistentialElim] = { - FP(keyword("obtain") ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ keyword("where") ~/ trigger.rep ~/ exp).map{ case (pos, (varList, t, e)) => PExistentialElim(varList.map { case (id,typ) => PLocalVarDecl(id,typ,None)(e.pos)}, t, e)(pos) } + FP(keyword("obtain") ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ keyword("where") ~/ trigger.rep ~/ exp).map { case (pos, (varList, t, e)) => PExistentialElim(varList.map { case (id, typ) => PLocalVarDecl(id, typ, None)(e.pos) }, t, e)(pos) } } /** Parser for universal introduction statements. */ - def universal_intro[_: P]: P[PUniversalIntro] = { - FP(keyword("prove") ~/ keyword("forall") ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ trigger.rep(sep = ",") ~/ keyword("assuming") ~/ exp ~/ keyword("implies") ~/ exp ~/ block).map { case (pos, (varList, triggers, e1, e2, b)) => PUniversalIntro(varList.map { case (id, typ) => PLocalVarDecl(id, typ, None)(e1.pos) }, triggers, e1, e2, b)(pos) } + def universal_intro[_: P]: P[PUniversalIntro] = + FP(keyword("prove") ~/ keyword("forall") ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ trigger.rep(sep = ",") ~/ keyword("assuming") ~/ exp ~/ keyword("implies") ~/ exp ~/ block).map { case (pos, (varList, triggers, e1, e2, b)) => PUniversalIntro(varList.map { case (id, typ) => PLocalVarDecl(id, typ, None)(e1.pos) }, triggers, e1, e2, b)(pos) } + + /** Parser for new influence by condition */ + def influenced_by[_: P]: P[PFlowAnnotation] = + //FP(keyword("influenced") ~/ (idndef ~ ":" ~ typ) ~/ keyword("by") ~ "{" ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ "}").map { case (pos, (v_idndef, v_typ, varList)) => PFlowAnalysis(v_idndef,v_typ, varList.map { case (id, typ) => (id, typ)})(pos)} + //FP(keyword("influenced") ~/ (idnuse) ~/ keyword("by") ~ "{" ~/ (idnuse).rep(sep = ",") ~/ "}").map { case (pos, (v_idnuse, varList)) => PFlowAnalysis(v_idnuse, varList)(pos) } + P(keyword("influenced") ~/ (influenced_by_var | influenced_by_heap)) + def influenced_by_var[_: P]: P[PFlowAnnotation] = { + FP(idnuse ~/ keyword("by") ~ "{" ~/ (heap_then_vars | vars_then_opt_heap) ~/ "}").map { + case (pos, (v_idnuse: PExp, (varList: Seq[PExp], None))) => + PFlowAnnotationVar(v_idnuse, varList)(pos) + case (pos, (v_idnuse: PExp, varList: Option[Seq[PExp]])) => + PFlowAnnotationVarHeapArg(v_idnuse, varList.getOrElse(Seq()))(pos) + case (pos, (v_idnuse: PExp, (varList1: Seq[PExp], varList2: Some[Seq[PExp]]))) => + PFlowAnnotationVarHeapArg(v_idnuse, varList1 ++ varList2.getOrElse(Seq()))(pos) + } + } + + def vars_then_opt_heap[_: P]: P[(Seq[PExp], Option[Seq[PExp]])] = { + P(idnuse.rep(sep = ",") ~/ ("," ~/ keyword("heap") ~/ ("," ~/ idnuse.rep(sep = ",")).?).?.map { + /** If there is no heap keyword */ + case None => None + + /** If there is the heap keyword but no further variables */ + case Some(None) => Some(Seq()) + + /** If there is the heap keyword and additionally further variables */ + case Some(Some(varList)) => Some(varList) + + }) + } + + def heap_then_vars[_: P]: P[Option[Seq[PExp]]] = { + P(keyword("heap") ~/ ("," ~/ idnuse.rep(sep = ",")).?) + } + + def influenced_by_heap[_: P]: P[PFlowAnnotation] = { + FP(keyword("heap") ~/ keyword("by") ~ "{" ~/ (heap_then_vars | vars_then_opt_heap) ~/ "}").map { + case (pos, (varList: Seq[PExp], None)) => + reportError(ParseError("The heap should always be influenced by the heap.", SourcePosition(pos._1.file,pos._1,pos._2))) + PFlowAnnotationHeap(varList)(pos) + case (pos, varList: Option[Seq[PExp]]) => + PFlowAnnotationHeapHeapArg(varList.getOrElse(Seq()))(pos) + case (pos, (varList1: Seq[PExp], varList2: Some[Seq[PExp]])) => + PFlowAnnotationHeapHeapArg(varList1 ++ varList2.getOrElse(Seq()))(pos) + } } /** Add existential elimination and universal introduction to the parser. */ override def beforeParse(input: String, isImported: Boolean): String = { // Add new keyword ParserExtension.addNewKeywords(Set[String]("obtain", "where", "prove", "forall", "assuming", "implies")) - // Add new parser to the precondition + ParserExtension.addNewKeywords(Set[String]("influenced", "by", "heap")) ParserExtension.addNewStmtAtEnd(existential_elim(_)) ParserExtension.addNewStmtAtEnd(universal_intro(_)) + // Add new parser to the precondition + /** doesn't work because of return value in precondition? */ + //ParserExtension.addNewPreCondition(influenced_by(_)) + // Add new parser to the postcondition + ParserExtension.addNewPostCondition(influenced_by(_)) input } @@ -69,40 +125,248 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, Expressions.instantiateVariables(exp, mapping.map(_._1.localVar), mapping.map(_._2.localVar)) } + /** check that influenced by expressions are exact or overapproximate the body of the method. */ + val body_graph_analysis: VarAnalysisGraph = VarAnalysisGraph(input, reportError) + input.methods.foreach(method => { + + val init_args_decl = method.formalArgs.map(a => body_graph_analysis.createInitialVertex(a)) + val init_rets_decl = method.formalReturns.map(r => body_graph_analysis.createInitialVertex(r)) + val method_vars: Map[LocalVarDecl, LocalVarDecl] = ((method.formalArgs zip init_args_decl) ++ (method.formalReturns zip init_rets_decl) ++ Seq((body_graph_analysis.heap_vertex, body_graph_analysis.createInitialVertex(body_graph_analysis.heap_vertex)))).toMap + val empty_body_graph = body_graph_analysis.createEmptyGraph(method_vars) + val body_graph = body_graph_analysis.compute_graph(empty_body_graph, method.body.getOrElse(Seqn(Seq(), Seq())()), method_vars) + //println("method body graph: " + body_graph_analysis.createDOT(body_graph)) + + val influenced_graph = body_graph_analysis.copyGraph(empty_body_graph) + method.posts.foreach { + case v@(FlowAnnotationVar(_, _) | FlowAnnotationVarHeapArg(_, _)) => + val (target, args) = v match { + case FlowAnnotationVar(t, a) => + (t, a) + case FlowAnnotationVarHeapArg(t, a) => + (t, a ++ Seq(body_graph_analysis.heap_vertex.localVar)) + } + + val target_var: LocalVar = target.asInstanceOf[LocalVar] + val target_decl: LocalVarDecl = LocalVarDecl(target_var.name, target_var.typ)(target_var.pos) + args.foreach(arg => { + val arg_var: LocalVar = arg.asInstanceOf[LocalVar] + val arg_decl: LocalVarDecl = LocalVarDecl(arg_var.name, arg_var.typ)(arg_var.pos) + influenced_graph.addEdge(method_vars(arg_decl), target_decl, new DefaultEdge) + }) + case h@(FlowAnnotationHeap(_) | FlowAnnotationHeapHeapArg(_)) => + val args = h match { + case FlowAnnotationHeap(a) => + a + case FlowAnnotationHeapHeapArg(a) => + a ++ Seq(body_graph_analysis.heap_vertex.localVar) + } + args.foreach(arg => { + val arg_var: LocalVar = arg.asInstanceOf[LocalVar] + val arg_decl: LocalVarDecl = LocalVarDecl(arg_var.name, arg_var.typ)(arg_var.pos) + influenced_graph.addEdge(method_vars(arg_decl), body_graph_analysis.heap_vertex, new DefaultEdge) + }) + } + + + /** ignore the edges from the .init_ret to the ret vertex since before the method there is no init value of a return variable. */ + method.formalReturns.foreach(r => { + body_graph.removeAllEdges(method_vars(r), r) + }) + + /** the set of all incoming edges to the return variables of the method body graph should be a subset of the set of the incoming edges of the influenced by graph */ + method.formalReturns.foreach(r => { + body_graph.incomingEdgesOf(r).forEach(e => { + if (!influenced_graph.containsEdge(body_graph.getEdgeSource(e), body_graph.getEdgeTarget(e))) { + val ret_sources: String = body_graph.incomingEdgesOf(r).asScala.map(e => body_graph.getEdgeSource(e).name).toSet.mkString(", ").replace(".init_", "").replace(".", "") + reportError(ConsistencyError("influenced by expression may be incorrect. Possible influenced by expression: \n" + "influenced " + r.name.replace(".", "") + " by {" + ret_sources + "}", r.pos)) + } + }) + }) + }) + + ViperStrategy.Slim({ - case e@ExistentialElim(v, trigs, exp) => { // e = ExistentialElim(vardecl, exp) - val (new_v_map,new_exp) = substituteWithFreshVars(v, exp) - val new_trigs = trigs.map{ case t => Trigger(t.exps.map{ case e1 => applySubstitution(new_v_map,e1)})(t.pos)} - Seqn( - Seq( - Assert(Exists(new_v_map.map(_._2), new_trigs, new_exp)(e.pos, ReasoningInfo))(e.pos) - ) + case e@ExistentialElim(v, trigs, exp) => // e = ExistentialElim(vardecl, exp) + val (new_v_map, new_exp) = substituteWithFreshVars(v, exp) + val new_trigs = trigs.map(t => Trigger(t.exps.map(e1 => applySubstitution(new_v_map, e1)))(t.pos)) + Seqn( + Seq( + Assert(Exists(new_v_map.map(_._2), new_trigs, new_exp)(e.pos, ReasoningInfo))(e.pos) + ) ++ - v.map { case variable => LocalVarDeclStmt(variable)(variable.pos) } //list of variables + v.map(variable => LocalVarDeclStmt(variable)(variable.pos)) //list of variables ++ Seq( Inhale(exp)(e.pos) ), - Seq() + Seq() )(e.pos) - } - case u@UniversalIntro(v, trigs, exp1, exp2, blk) => { + /** remove the influenced by postconditions. */ + case m: Method => + var postconds: Seq[Exp] = Seq() + m.posts.foreach { + case _: FlowAnnotation => + postconds = postconds + case s@_ => + postconds = postconds ++ Seq(s) + } + val newMethod = + if (postconds != m.posts) { + m.copy(pres = m.pres, posts = postconds)(m.pos, m.info, m.errT) + } else { + m + } + newMethod + + case u@UniversalIntro(v, trigs, exp1, exp2, blk) => val boolvar = LocalVarDecl(uniqueName("b"), Bool)(exp1.pos) - val (new_v_map,new_exp1) = substituteWithFreshVars(v, exp1) + /** check whether immutable universal introduction variables are reassigned + * if non empty set returned, immutable variables contained in there might have been reassigned. */ + /* + val reassigned = check_is_reassigned(v,blk) + if (reassigned.nonEmpty) { + val reassigned_names: String = reassigned.mkString(", ") + val reassigned_pos: String = reassigned.map(_.pos).mkString(", ") + reportError(ConsistencyError("Universal Introduction variable(s) (" + reassigned_names + ") might have been reassigned at position(s) (" + reassigned_pos + ")", u.pos)) + } + */ + + + /** + * get all variables that are assigned to inside the block and take intersection with universal introduction + * variables. If they are contained throw error since these variables should be immutable + */ + val written_vars: Option[Set[LocalVarDecl]] = writtenTo(blk) + if (written_vars.isDefined) { + val reassigned_vars: Set[LocalVarDecl] = written_vars.get.intersect(v.toSet) + if (reassigned_vars.nonEmpty) { + val reassigned_names: String = reassigned_vars.mkString(", ") + val reassigned_pos: String = reassigned_vars.map(_.pos).mkString(", ") + reportError(ConsistencyError("Universal Introduction variable(s) (" + reassigned_names + ") might have been reassigned at position(s) (" + reassigned_pos + ")", u.pos)) + } + } + + + val vars_outside_blk: mutable.Set[Declaration] = mutable.Set() + + /** Get all variables that are in scope in the current method but not inside the block */ + input.methods.foreach(m => m.body.get.ss.foreach(s => { + if (s.contains(u)) { + vars_outside_blk ++= mutable.Set(m.transitiveScopedDecls: _*) + } + })) + /** Variables declared in the universal introduction statement are tainted */ + val tainted: Set[LocalVarDecl] = v.toSet + + /* + /** + * SET VERSION + */ + /** check whether any additional variables are tainted inside of the block */ + var all_tainted = Set[Declaration]() + all_tainted = get_tainted_vars_stmt(tainted, blk) + + + /** remove the variables that were tainted to begin with */ + vars_outside_blk --= mutable.Set(u.transitiveScopedDecls: _*) + + /** check whether any variables were tainted that are declared outside of our block */ + if (!(vars_outside_blk.intersect(all_tainted).isEmpty)) { + val tainted_vars: Set[Declaration] = all_tainted.intersect(vars_outside_blk) + val problem_vars: String = tainted_vars.mkString(", ") + val problem_pos: String = tainted_vars.map(vs => vs.pos).mkString(", ") + reportError(ConsistencyError("Universal introduction variable might have been assigned to variable " + problem_vars + " at positions (" + problem_pos + "), defined outside of the block", u.pos)) + } + */ + + /** + * GRAPH VERSION + */ + + val graph_analysis: VarAnalysisGraph = VarAnalysisGraph(input, reportError) + + /** create graph with vars that are in scope */ + vars_outside_blk --= mutable.Set(u.transitiveScopedDecls: _*) + vars_outside_blk ++= v + var graph: Graph[LocalVarDecl, DefaultEdge] = new DefaultDirectedGraph[LocalVarDecl, DefaultEdge](classOf[DefaultEdge]) + + /** old graph */ + //vars_outside_blk.foreach(v => graph.addVertex(v)) + + /** new graph */ + + var allVertices: Map[LocalVarDecl, LocalVarDecl] = Map[LocalVarDecl, LocalVarDecl]() + /* + var allVertices: Set[(LocalVarDecl, LocalVarDecl)] = Set() + */ + /** add heap variables to vertices */ + allVertices += (graph_analysis.heap_vertex -> graph_analysis.createInitialVertex(graph_analysis.heap_vertex)) + + vars_outside_blk.foreach(v => { + val v_decl = v.asInstanceOf[LocalVarDecl] + val v_init = graph_analysis.createInitialVertex(v_decl) + allVertices += (v_decl -> v_init) + /* + allVertices = allVertices + ((v_init,v_decl)) + */ + graph.addVertex(v_init) + graph.addVertex(v_decl) + }) + //println(allVertices) + //println("first graph:") + //println(createDOT(graph)) + /** old graph */ + //graph = compute_graph(graph, blk) + + /** new graph */ + //println("ReasoningPlugin initial graph:", graph) + /* + graph = compute_graph(graph, blk, allVertices) + */ + + graph = graph_analysis.compute_graph(graph, blk, allVertices) + + //for debugging + //println("ReasoningPlugin final Graph") + //println(graph_analysis.createDOT(graph)) + + var noEdges: Boolean = true + var badEdges = Set[DefaultEdge]() + tainted.foreach(v => { + if (graph.edgesOf(graph_analysis.createInitialVertex(v)).size() > 1) { + badEdges = badEdges ++ graph.edgesOf(graph_analysis.createInitialVertex(v)).asScala.toSet[DefaultEdge] + noEdges = false + } + //noEdges = noEdges && graph.edgesOf(createInitialVertex(v)).size() <= 1 + }) + if (!noEdges) { + //println("entered not empty thingy") + var tainted_vars: Set[LocalVarDecl] = Set() + //graph.edgeSet().forEach(e => { + badEdges.foreach(e => { + //println("edge inside:", e) + val target = graph.getEdgeTarget(e) + if (!tainted.contains(target)) { + tainted_vars = tainted_vars + graph.getEdgeTarget(e) + } + }) + val problem_vars: String = tainted_vars.mkString(", ") + val problem_pos: String = tainted_vars.map(vs => vs.pos).mkString(", ") + reportError(ConsistencyError("Universal introduction variable might have been assigned to variable " + problem_vars + " at positions (" + problem_pos + "), defined outside of the block", u.pos)) + } + + + val (new_v_map, new_exp1) = substituteWithFreshVars(v, exp1) val new_exp2 = applySubstitution(new_v_map, exp2) - val arb_vars = new_v_map.map{case vars => vars._2} - val new_trigs = trigs.map{ case t => Trigger(t.exps.map{ case e1 => applySubstitution(new_v_map,e1)})(t.pos)} + val arb_vars = new_v_map.map(vars => vars._2) + val new_trigs = trigs.map(t => Trigger(t.exps.map(e1 => applySubstitution(new_v_map, e1)))(t.pos)) // label should also be in used Names => can also use uniqueName function val lbl = uniqueName("l") + + Seqn( - Seq( - LocalVarDeclStmt(boolvar)(u.pos) - ) - ++ - v.map{ case vars => LocalVarDeclStmt(vars)(u.pos)} - ++ Seq( Label(lbl, Seq())(u.pos), If(boolvar.localVar, @@ -115,29 +379,38 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, )(exp1.pos), blk, - Assert(Implies(boolvar.localVar,exp2)(exp2.pos))(exp2.pos), - Inhale(Forall(arb_vars,new_trigs, Implies(LabelledOld(new_exp1,lbl)(exp2.pos), new_exp2)(exp2.pos))(exp2.pos))(exp2.pos) + Assert(Implies(boolvar.localVar, exp2)(exp2.pos))(exp2.pos), + Inhale(Forall(arb_vars, new_trigs, Implies(LabelledOld(new_exp1, lbl)(exp2.pos), new_exp2)(exp2.pos))(exp2.pos))(exp2.pos) ), - Seq() + Seq(boolvar) ++ v )(exp1.pos) - } }, Traverse.TopDown).execute(input) } + /* override def mapVerificationResult(input: VerificationResult): VerificationResult = { - val errors: Seq[AbstractError] = (input match { + val errors: Seq[AbstractError] = input match { case Success => Seq() - case Failure(errors) => { + case Failure(errors) => errors.map { - case AssertFailed(a, err_reason, c) if a.info == ReasoningInfo => { - ExistentialElimFailed(a, err_reason, c) - } + case af@AssertFailed(a, err_reason, c) => + if (a.info == ExistentialElim) { + ExistentialElimFailed(a, err_reason, c) + } + else if (a.info == UniversalIntro) { + UniversalIntroFailed(a, err_reason, c) + } else if ((a.info == FlowAnnotationVar) || (a.info == FlowAnnotationHeap) || a.info == FlowAnnotationVarHeapArg || a.info == FlowAnnotationHeapHeapArg) { + FlowAnalysisFailed(a, err_reason, c) + } else { + af + } + } - } - }) + } if (errors.length == 0) Success else Failure(errors) } + */ } \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala new file mode 100644 index 000000000..daa625958 --- /dev/null +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala @@ -0,0 +1,986 @@ +package viper.silver.plugin.standard.reasoning.analysis + +import org.jgrapht.Graph +import org.jgrapht.graph.{AbstractBaseGraph, DefaultDirectedGraph, DefaultEdge} +import viper.silver.ast.{AccessPredicate, Assert, Assume, BinExp, CurrentPerm, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, ForPerm, FuncApp, If, Inhale, Int, Label, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Perm, Program, Ref, Seqn, Stmt, UnExp, While} +import viper.silver.plugin.standard.reasoning.{ExistentialElim, FlowAnnotationHeap, FlowAnnotationHeapHeapArg, FlowAnnotationVar, FlowAnnotationVarHeapArg, UniversalIntro} +import viper.silver.verifier.AbstractError + +import java.io.StringWriter +import scala.jdk.CollectionConverters._ +import scala.util.control.Breaks.break + + + +case class VarAnalysisGraph(prog: Program, + reportErrorWithMsg: AbstractError => Unit) { + + val prefix: String = ".init_" + + val heap_vertex: LocalVarDecl = LocalVarDecl(".heap", Ref)() + + + /** + * Creates the Vertex that represents the initial value of the variable before the statement is executed + * @param variable Variable for which we want to create the Vertex which represents the initial value of the variable + * @return a LocalVariableDeclaration + */ + def createInitialVertex(variable:LocalVarDecl): LocalVarDecl = { + LocalVarDecl(prefix + variable.name, variable.typ)(variable.pos) + } + + /** + * creates a graph with no edges and only the vertices + * @param vertices represent the variables that are in scope + * @return an graph with only vertices + */ + def createEmptyGraph(vertices: Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl, DefaultEdge] = { + val graph: Graph[LocalVarDecl,DefaultEdge] = new DefaultDirectedGraph[LocalVarDecl,DefaultEdge](classOf[DefaultEdge]) + for ((v,v_init)<-vertices) { + graph.addVertex(v_init) + graph.addVertex(v) + } + graph + } + + + /** + * create a Graph that contains all the vertices with an edge from edge vertex representing the initial value of the variable to the 'end'-value of the variable + * @param vertices represent the variables that are in scope + * @return an identity graph + */ + def createIdentityGraph(vertices: Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl, DefaultEdge] = { + val graph: Graph[LocalVarDecl, DefaultEdge] = createEmptyGraph(vertices) + for ((v,v_init)<-vertices) { + graph.addEdge(v_init, v) + } + graph + } + + /** + * add Edges from the vertices representing the initial value to the vertices representing its 'end'-values if they have no incoming edge yet + * @param graph existing graph + * @param vertices the vertices representing variables which should be checked + * @return graph + */ + def addMissingEdges(graph: Graph[LocalVarDecl,DefaultEdge], vertices:Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl, DefaultEdge] = { + + for ((v,v_init)<-vertices) { + if (graph.incomingEdgesOf(v).isEmpty) { + graph.addEdge(v_init, v, new DefaultEdge) + } + } + graph + } + + /** + * @param graph graph that should be translated to DOT-language + * @return String that is the graph in DOT-language + * + */ + def createDOT(graph: Graph[LocalVarDecl, DefaultEdge]): String = { + val writer: StringWriter = new StringWriter() + writer.write("strict digraph G {\n") + graph.vertexSet().forEach(v => { + writer.write(" " + v.name.replace(".","") + ";\n") + }) + graph.edgeSet().forEach(e => { + writer.write(" " + graph.getEdgeSource(e).name.replace(".","") + " -> " + graph.getEdgeTarget(e).name.replace(".","") + ";\n") + }) + writer.write("}\n") + writer.toString + } + + /** + * returns all the variables inside an expression + * @param graph existing graph + * @param exp expressions from which all variables should be returned + * @return set of Variable declarations + */ + def getVarsFromExpr(graph: Graph[LocalVarDecl, DefaultEdge], exp: Exp): Set[LocalVarDecl] = { + val vars: Set[LocalVarDecl] = Set() + exp match { + case l@LocalVar(_, _) => + var l_decl: LocalVarDecl = LocalVarDecl("", Int)() + graph.vertexSet().forEach(v => if (v.name == l.name) { + l_decl = v + }) + if (l_decl.name == "") { + l_decl = LocalVarDecl(l.name, l.typ)() + } + vars + l_decl + + case BinExp(exp1, exp2) => + getVarsFromExpr(graph, exp1) ++ getVarsFromExpr(graph, exp2) + + case UnExp(exp) => + getVarsFromExpr(graph, exp) + + case FuncApp(_,exps) => + var allVars = vars + if (!vars.contains(heap_vertex)) { + allVars += heap_vertex + } + exps.foreach(e => { + val exp_vars = getVarsFromExpr(graph, e) + exp_vars.foreach(v => { + if (v.typ != Ref) { + allVars += v + } + }) + }) + allVars + + case DomainFuncApp(_,exps,_) => + var allVars = vars + exps.foreach(e => { + val exp_vars = getVarsFromExpr(graph, e) + exp_vars.foreach(v => { + allVars += v + }) + }) + allVars + + case _: ForPerm | _: CurrentPerm => + if (!vars.contains(heap_vertex)) { + vars + heap_vertex + } else { + vars + } + + + case FieldAccess(v,_) => + getVarsFromExpr(graph, v) + + case AccessPredicate(access, _) => + /** Should only be the case in e.g.an inhale or an exhale statement */ + var allVars = vars + val access_vars = getVarsFromExpr(graph, access) + access_vars.foreach(v => { + allVars += v + }) + allVars + + case _ => + Set() + } + } + + /** + * returns a shallow copy of graph instance, neither Vertices nor Edges are cloned + * @param graph graph that should be copied. + * @return copied graph + */ + def copyGraph(graph: Graph[LocalVarDecl, DefaultEdge]): Graph[LocalVarDecl, DefaultEdge] = { + val copied_graph = graph.asInstanceOf[AbstractBaseGraph[LocalVarDecl, DefaultEdge]].clone().asInstanceOf[DefaultDirectedGraph[LocalVarDecl, DefaultEdge]] + copied_graph + } + + /** + * takes to graphs and returns a new graph containing the union of the edges of both input graphs. Both graphs should contain the same vertices! + * @param graph1 + * @param graph2 + * @return graph + */ + def unionEdges(graph1: Graph[LocalVarDecl, DefaultEdge], graph2: Graph[LocalVarDecl, DefaultEdge]): Graph[LocalVarDecl,DefaultEdge] = { + val new_graph = copyGraph(graph1) + if (graph1.vertexSet().equals(graph2.vertexSet())) { + for (e2: DefaultEdge <- graph2.edgeSet().asScala.toSet) { + if (!new_graph.containsEdge(e2)) { + val src = graph2.getEdgeSource(e2) + val trgt = graph2.getEdgeTarget(e2) + new_graph.addEdge(src, trgt, e2) + } + } + } else { + /** TODO: Should error be thrown? Should not happen */ + } + new_graph + } + + /** + * merges two graphs. Meaning: we create a new graph with all the init vertices from graph one and all 'end' vertices from graph two. + * We assume that all 'end' vertices from graph1 can be matched with an init vertex from graph2. E.g. v = .init_v + * We then add an edge from a to b if there is a path from a to b. + * @param graph1 + * @param graph2 + * @param vertices + * @return merged graph + */ + def mergeGraphs(graph1: Graph[LocalVarDecl, DefaultEdge], graph2: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl, LocalVarDecl]): Graph[LocalVarDecl,DefaultEdge] = { + val new_graph = createEmptyGraph(vertices) + for (e1: DefaultEdge <- graph1.edgeSet().asScala.toSet) { + val src = graph1.getEdgeSource(e1) + val trgt = graph1.getEdgeTarget(e1) + val init_trgt = vertices.get(trgt) + if (init_trgt.isDefined) { + for (e2: DefaultEdge <- graph2.outgoingEdgesOf(init_trgt.get).asScala.toSet) { + new_graph.addEdge(src, graph2.getEdgeTarget(e2), new DefaultEdge) + } + } else { + /** TODO: Should technically not happen */ + } + } + new_graph + } + + + + def compute_graph(graph: Graph[LocalVarDecl,DefaultEdge], stmt: Stmt, vertices: Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl,DefaultEdge] = { + stmt match { + case Seqn(ss, scopedSeqnDeclarations) => + var allVertices: Map[LocalVarDecl,LocalVarDecl] = vertices + for (d <- scopedSeqnDeclarations) { + val d_decl = d.asInstanceOf[LocalVarDecl] + val d_init = createInitialVertex(d_decl) + allVertices += (d_decl -> d_init) + } + var new_graph: Graph[LocalVarDecl, DefaultEdge] = createIdentityGraph(allVertices) + for (s <- ss) { + val graph_copy = copyGraph(new_graph) + val comp_graph = compute_graph(graph_copy, s, allVertices) + + new_graph = mergeGraphs(new_graph, comp_graph, allVertices) + } + + val final_graph: Graph[LocalVarDecl, DefaultEdge] = createEmptyGraph(vertices) + new_graph.edgeSet().forEach(e => { + val source: LocalVarDecl = new_graph.getEdgeSource(e) + val target: LocalVarDecl = new_graph.getEdgeTarget(e) + if (final_graph.containsVertex(source) && final_graph.containsVertex(target)) { + final_graph.addEdge(source, target, e) + } + }) + final_graph + + case If(cond, thn, els) => + val id_graph = createIdentityGraph(vertices) + val expr_vars = getVarsFromExpr(id_graph, cond) + val cond_graph = copyGraph(id_graph) + val thn_graph = compute_graph(copyGraph(id_graph), thn, vertices) + val els_graph = compute_graph(copyGraph(id_graph), els, vertices) + val writtenToThn = writtenTo(vertices, thn).getOrElse(Set()) + val writtenToEls = writtenTo(vertices, els).getOrElse(Set()) + val allWrittenTo = writtenToThn ++ writtenToEls + for (w <- allWrittenTo) { + if (cond_graph.containsVertex(w)) { + for (v <- expr_vars) { + val v_init = vertices(v) + cond_graph.addEdge(v_init, w, new DefaultEdge) + } + } + } + writtenToThn.intersect(writtenToEls).foreach(v => { + cond_graph.removeEdge(vertices(v),v) + }) + val thn_els_graph = unionEdges(thn_graph, els_graph) + val res_graph = unionEdges(cond_graph, thn_els_graph) + res_graph + + case w@While(cond, _, body) => + val graph_copy: Graph[LocalVarDecl, DefaultEdge] = copyGraph(graph) + + /** analyse one iteration of the while loop */ + var new_graph: Graph[LocalVarDecl, DefaultEdge] = compute_graph(graph_copy, If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), vertices) + new_graph = mergeGraphs(graph_copy, new_graph, vertices) + + /** check whether the edges are equal. + * First check whether both edge sets have the same size + * then go through each edge and check whether it also exists in the new graph */ + var edges_equal: Boolean = true + val equal_size: Boolean = graph.edgeSet().size().equals(new_graph.edgeSet().size()) + if (equal_size && new_graph.vertexSet().equals(graph.vertexSet())) { + for (e1: DefaultEdge <- new_graph.edgeSet().asScala.toSet) { + if (graph.getEdge(new_graph.getEdgeSource(e1), new_graph.getEdgeTarget(e1)) == null) { + edges_equal = false + compute_graph(new_graph, w, vertices) + break() + } + } + graph + } else { + compute_graph(new_graph, w, vertices) + } + + case LocalVarAssign(lhs,rhs) => + var new_graph: Graph[LocalVarDecl,DefaultEdge] = createEmptyGraph(vertices) + val rhs_vars = getVarsFromExpr(new_graph, rhs) + val lhs_decl: LocalVarDecl = LocalVarDecl(lhs.name,lhs.typ)(lhs.pos) + + for (v <- rhs_vars) { + /** if the variable on the right hand side is a field access */ + if (v.typ == Ref || v.typ == Perm) { + val heap_init = vertices(heap_vertex) + new_graph.addEdge(heap_init, lhs_decl, new DefaultEdge) + } else { + val v_init = vertices(v) + new_graph.addEdge(v_init, lhs_decl, new DefaultEdge) + } + } + + /** Since variables that are not assigned to should have an edge from their initial value to their 'end'-value */ + val vert_wout_lhs = vertices - lhs_decl + new_graph = addMissingEdges(new_graph, vert_wout_lhs) + new_graph + + case Inhale(exp) => + val id_graph = createIdentityGraph(vertices) + if (exp.isPure) { + graph + } else { + val inhale_vars = getVarsFromExpr(graph, exp) + inhale_vars.foreach(v => { + if (v.typ == Ref) { + val init_v = createInitialVertex(v) + id_graph.addEdge(init_v, heap_vertex, new DefaultEdge) + } + }) + id_graph + } + + case Exhale(exp) => + val id_graph = createIdentityGraph(vertices) + if (exp.isPure) { + graph + } else { + val exhale_vars = getVarsFromExpr(graph, exp) + exhale_vars.foreach(v => { + if (v.typ == Ref) { + val init_v = createInitialVertex(v) + id_graph.addEdge(init_v, heap_vertex, new DefaultEdge) + } + }) + id_graph + } + + case Assume(_) => + graph + + case Label(_, _) => + graph + + case MethodCall(methodName, args, targets) => + val met = prog.findMethod(methodName) + /** create graph from each variable in each expression to the according method variable */ + val methodcall_graph: Graph[LocalVarDecl, DefaultEdge] = createEmptyGraph(vertices) + + createInfluencedByGraph(methodcall_graph,vertices,args,targets,met.formalArgs, met.formalReturns,met.posts) + + + case FieldAssign(_, rhs) => + val id_graph = createIdentityGraph(vertices) + val rhs_vars = getVarsFromExpr(graph, rhs) + rhs_vars.foreach(v => { + /** Edge from .init_heap to heap does not have to be added since it exists anyways */ + if (v.typ == Ref || v.typ == Perm || v == heap_vertex) { + id_graph + } else { + val v_init = createInitialVertex(v) + id_graph.addEdge(v_init, heap_vertex, new DefaultEdge) + } + }) + id_graph + + case ExistentialElim(_,_,_) => graph + + case UniversalIntro(varList,_,_,_,blk) => + val new_vertices: Map[LocalVarDecl, LocalVarDecl] = vertices ++ varList.map(v => (v,createInitialVertex(v))) + compute_graph(graph,blk,new_vertices) + + case Assert(_) => graph + + case s@_ => + throw new UnsupportedOperationException("undefined statement for universal introduction block: " + s) + } + } + + def createInfluencedByGraph(graph: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl,LocalVarDecl], arg_names: Seq[Exp], ret_names: Seq[LocalVar], method_arg_names: Seq[LocalVarDecl], method_ret_names: Seq[LocalVarDecl], posts: Seq[Exp]): Graph[LocalVarDecl, DefaultEdge] = { + /** set of all target variables that have not been included in the influenced by expression up until now */ + var retSet: Set[LocalVarDecl] = method_ret_names.toSet + heap_vertex + var methodcall_graph = copyGraph(graph) + val method_arg_names_incl_heap = method_arg_names ++ Seq(heap_vertex) + + /** create .arg_ declaration for each argument */ + var method_args: Map[LocalVarDecl, LocalVarDecl] = Map() + var method_arg_counter: Int = 0 + method_arg_names_incl_heap.foreach(method_arg => { + method_args += (method_arg -> LocalVarDecl(".arg" + method_arg_counter, method_arg.typ)(method_arg.pos)) + method_arg_counter += 1 + }) + + /** create .ret_ declaration for each return variable */ + var method_rets: Map[LocalVarDecl, LocalVarDecl] = Map() + var method_ret_counter: Int = 0 + retSet.foreach(method_ret => { + method_rets += (method_ret -> LocalVarDecl(".ret" + method_ret_counter, method_ret.typ)(method_ret.pos)) + method_ret_counter += 1 + }) + + /** contains all variables that are passed to the method */ + var total_arg_decls: Set[LocalVarDecl] = Set(heap_vertex) + + /** add edges from method arguments to .arg variables */ + (arg_names zip method_arg_names).foreach(arg => { + /** extract all variables in expressions that are added to the method */ + val arg_decls: Set[LocalVarDecl] = getVarsFromExpr(graph, arg._1) + total_arg_decls ++= arg_decls + arg_decls.foreach(arg_decl => { + if (!methodcall_graph.containsVertex(vertices(arg_decl))) { + methodcall_graph.addVertex(vertices(arg_decl)) + } + if (!methodcall_graph.containsVertex(method_args(arg._2))) { + methodcall_graph.addVertex(method_args(arg._2)) + } + /** add edge from .init variable to .arg variable */ + methodcall_graph.addEdge(vertices(arg_decl), method_args(arg._2)) + }) + }) + + /** add heap and corresponding .arg variable as method argument */ + if(!methodcall_graph.containsVertex(heap_vertex)) { + methodcall_graph.addVertex(vertices(heap_vertex)) + } + if(!methodcall_graph.containsVertex(method_args(heap_vertex))) { + methodcall_graph.addVertex(method_args(heap_vertex)) + } + methodcall_graph.addEdge(vertices(heap_vertex),method_args(heap_vertex)) + + + /** need to add the edges from the influenced by expression */ + posts.foreach { + case v@(FlowAnnotationVar(_,_) | FlowAnnotationVarHeapArg(_,_)) => + /** depending on whether or not heap is method argument get argument in influenced by statement */ + val (returned, arguments) = v match { + case FlowAnnotationVar(r,a) => + (r,a) + case FlowAnnotationVarHeapArg(r,a) => + (r,a++Seq(heap_vertex.localVar)) + } + + /** returned has to be instance of LocalVar */ + val returned_var: LocalVar = returned.asInstanceOf[LocalVar] + /** create LocalVarDecl such that it can be added in the graph */ + val return_decl = LocalVarDecl(returned_var.name, returned_var.typ)(returned_var.pos) + retSet -= return_decl + + if (!methodcall_graph.containsVertex(method_rets(return_decl))) { + methodcall_graph.addVertex(method_rets(return_decl)) + } + + arguments.foreach(argument => { + /** argument has to be instance of LocalVar */ + val argument_var: LocalVar = argument.asInstanceOf[LocalVar] + val argument_decl = LocalVarDecl(argument_var.name, argument_var.typ)(argument_var.pos) + /** get corresponding .arg variable and add edge from .arg to .ret vertex */ + val prov_decl = method_args(argument_decl) + methodcall_graph.addEdge(prov_decl, method_rets(return_decl), new DefaultEdge) + }) + case h@(FlowAnnotationHeap(_) | FlowAnnotationHeapHeapArg(_) )=> + if (!methodcall_graph.containsVertex(method_rets(heap_vertex))) { + methodcall_graph.addVertex(method_rets(heap_vertex)) + } + retSet -= heap_vertex + + val arguments = h match { + case FlowAnnotationHeap(a) => + a + case FlowAnnotationHeapHeapArg(a) => + a ++ Seq(heap_vertex.localVar) + } + + arguments.foreach(argument => { + /** argument has to be instance of LocalVar */ + val argument_var: LocalVar = argument.asInstanceOf[LocalVar] + val argument_decl = LocalVarDecl(argument_var.name, argument_var.typ)(argument_var.pos) + val prov_decl = method_args(argument_decl) + methodcall_graph.addEdge(prov_decl, method_rets(heap_vertex), new DefaultEdge) + }) + + } + + /** now need to add to graph the edges from the method return variables to the target variables */ + val targets_decl: Seq[LocalVarDecl] = ret_names.map(t => { + graph.vertexSet().asScala.filter(lvd => lvd.name == t.name).head + }) ++ Seq(heap_vertex) + ((method_ret_names ++ Seq(heap_vertex)) zip targets_decl).foreach(ret => { + if (!methodcall_graph.containsVertex(ret._2)) { + methodcall_graph.addVertex(ret._2) + } + if (!methodcall_graph.containsVertex(method_rets(ret._1))) { + methodcall_graph.addVertex(method_rets(ret._1)) + } + /** add edge from .ret variable to target variable */ + methodcall_graph.addEdge(method_rets(ret._1), ret._2) + }) + + + /** add edges from all method argument to each return variable that wasn't mentioned in the influenced by statement */ + retSet.foreach(r => { + method_arg_names_incl_heap.foreach(a => { + if (!methodcall_graph.containsVertex(method_args(a))) { + methodcall_graph.addVertex(method_args(a)) + } + if (!methodcall_graph.containsVertex(method_rets(r))) { + methodcall_graph.addVertex(method_rets(r)) + } + methodcall_graph.addEdge(method_args(a), method_rets(r), new DefaultEdge) + }) + }) + + + var copy_arg_graph = copyGraph(methodcall_graph) + + /** remove edge from .ret_ vertex to the final vertex */ + for (elem <- targets_decl) { + /** get all edges from target variables to .ret variables */ + copy_arg_graph.incomingEdgesOf(elem).forEach(inc_edge => { + //should only be one edge + val ret_vert = methodcall_graph.getEdgeSource(inc_edge) + /** get edges from .ret variable to .arg variable */ + copy_arg_graph.incomingEdgesOf(ret_vert).forEach(ret_inc_e => { + val arg_vert = methodcall_graph.getEdgeSource(ret_inc_e) + /** add edge from .arg variable to target variable */ + methodcall_graph.addEdge(arg_vert, elem) + }) + /** remove .ret variables */ + methodcall_graph.removeVertex(ret_vert) + }) + } + + + /** remove edge from the .arg_ to the .init vertex */ + copy_arg_graph = copyGraph(methodcall_graph) + /** go through .init variables */ + for (elem <- total_arg_decls + heap_vertex) { + /** go through all outgoing edges of .init variable */ + copy_arg_graph.outgoingEdgesOf(vertices(elem)).forEach(out_edge => { + /** get the .arg variable that edge leads to */ + val arg_vert = methodcall_graph.getEdgeTarget(out_edge) + /** get edges from .arg variable to the target variable */ + copy_arg_graph.outgoingEdgesOf(arg_vert).forEach(arg_out_e => { + val final_vert = methodcall_graph.getEdgeTarget(arg_out_e) + /** create edge from .init variable to target variable */ + methodcall_graph.addEdge(vertices(elem), final_vert) + }) + methodcall_graph.removeVertex(arg_vert) + }) + } + + /** Since variables that are not assigned to should have an edge from their initial value to their 'end'-value */ + val vert_wout_lhs = vertices.removedAll(targets_decl) + methodcall_graph = addMissingEdges(methodcall_graph, vert_wout_lhs) + methodcall_graph + } + + + def writtenTo(vertices: Map[LocalVarDecl,LocalVarDecl], stmt: Stmt): Option[Set[LocalVarDecl]] = { + var output: Option[Set[LocalVarDecl]] = None + stmt match { + case Seqn(ss, _) => + for (s <- ss) { + output match { + case None => output = writtenTo(vertices, s) + case Some(v) => output = Some(v ++ writtenTo(vertices, s).getOrElse(Set[LocalVarDecl]())) + } + + } + output + case LocalVarAssign(lhs, _) => + var res: Option[Set[LocalVarDecl]] = None + for (vs <- vertices) { + if (vs._1.name == lhs.name) { + res = Some(Set(vs._1)) + } else { + /** This is the case if the variable is in scope in e.g. a then or an else block. */ + res = Some(Set(LocalVarDecl(lhs.name, lhs.typ)(lhs.pos))) + } + } + res + case If(_, thn, els) => + val writtenThn: Option[Set[LocalVarDecl]] = writtenTo(vertices, thn) + val writtenEls: Option[Set[LocalVarDecl]] = writtenTo(vertices, els) + (writtenThn, writtenEls) match { + case (None, None) => None + case (Some(_), None) => writtenThn + case (None, Some(_)) => writtenEls + case (Some(t), Some(e)) => Some(t ++ e) + } + + case While(_, _, body) => + writtenTo(vertices, body) + + case MethodCall(_, _, _) => + None + + + case Inhale(_) => + None + + case Assume(_) => + None + + case Label(_, _) => + None + + case _ => + None + } + } + + +} + + +/** + * ************************************ + * * + * old Graph version * + * * + * ************************************ + */ +/* +trait VarAnalysisGraph { + def reportErrorWithMsg(error: AbstractError): Unit + + /** + * + * @param graph to which vertices should be added + * @param scopedSeqnDeclarations variable declaration inside this codeblock + * @return graph with the newly added vertices + */ + def addNodes(graph: Graph[Declaration, DefaultEdge], scopedSeqnDeclarations: Seq[Declaration]): Graph[Declaration, DefaultEdge] = { + val result_graph: Graph[Declaration, DefaultEdge] = graph + scopedSeqnDeclarations.foreach(decl => result_graph.addVertex(decl)) + result_graph + } + + /** + * @param graph + * @return String that is the graph in DOT-language + * + */ + + def createDOT(graph:Graph[Declaration, DefaultEdge]) : String = { + val writer: StringWriter = new StringWriter() + writer.write("strict digraph G {\n") + graph.vertexSet().forEach(v => { + writer.write(" " + v.name + ";\n") + }) + graph.edgeSet().forEach(e => { + writer.write(" " + graph.getEdgeSource(e) + " -> " + graph.getEdgeTarget(e) + ";\n") + }) + writer.write("}\n") + writer.toString + } + + /** + * + * @param graph existing graph + * @param exp expressions from which all variables should be returned + * @return set of Variable declarations + */ + def getVarsFromExpr(graph: Graph[Declaration, DefaultEdge], exp: Exp): Set[Declaration] = { + val vars: Set[Declaration] = Set() + exp match { + case l@LocalVar(_, _) => { + var l_decl: Declaration = LocalVarDecl("", Int)() + graph.vertexSet().forEach(v => if (v.name == l.name) { l_decl = v }) + if (l_decl.name == "") { + l_decl = LocalVarDecl(l.name,l.typ)() + } + vars + l_decl + } + case BinExp(exp1, exp2) => { + getVarsFromExpr(graph, exp1) ++ getVarsFromExpr(graph, exp2) + } + case UnExp(exp) => { + getVarsFromExpr(graph, exp) + } + case _ => Set() + } + } + + /** + * + * @param graph graph that should be copied. Note: Shallow copy of graph instance, neither Vertices nor Edges are cloned + * @return copied graph + */ + def copyGraph(graph: Graph[Declaration, DefaultEdge]): Graph[Declaration, DefaultEdge] = { + val copied_graph = graph.asInstanceOf[AbstractBaseGraph[Declaration,DefaultEdge]].clone().asInstanceOf[DefaultDirectedGraph[Declaration, DefaultEdge]] + copied_graph + } + + /** + * + * @param graph + * @param src src vertex of edge (basically righthand-side of the assignment + * @param target target vertex of edge (basically lefthand-side of the assigment + * @return Graph with the new edge included + */ + def addTransitiveEdge(graph: Graph[Declaration, DefaultEdge], src: Declaration, target: Declaration): Graph[Declaration, DefaultEdge] = { + val new_graph = copyGraph(graph) + // the first condition depends on whether or not we want to include self loops + if (/*!src.equals(target) && */!new_graph.containsEdge(src, target)) { + new_graph.addEdge(src, target, new DefaultEdge) + /** go on level up and add those edges as well, this is enough + * since our invariant tells us that our graph is always transitively closed */ + new_graph.incomingEdgesOf(src).forEach(e => { + val src1 = new_graph.getEdgeSource(e) + /** edge only needs to be added if it doesn't exist yet */ + if (!new_graph.containsEdge(src1, target)) { + new_graph.addEdge(src1, target, new DefaultEdge) + } + }) + } + new_graph + } + + def compute_graph(graph: Graph[Declaration, DefaultEdge], stmt: Stmt): Graph[Declaration, DefaultEdge] = { + stmt match { + case Seqn(ss, scopedSeqnDeclarations) => { + val graph_copy: Graph[Declaration, DefaultEdge] = copyGraph(graph) + var new_graph = addNodes(graph_copy, scopedSeqnDeclarations) + for (s <- ss) { + val new_graph_copy: Graph[Declaration,DefaultEdge] = copyGraph(new_graph) + new_graph = compute_graph(new_graph_copy, s) + } + val final_graph: Graph[Declaration, DefaultEdge] = new DefaultDirectedGraph[Declaration, DefaultEdge](classOf[DefaultEdge]) + graph.vertexSet().forEach(v => final_graph.addVertex(v)) + new_graph.edgeSet().forEach(e => { + val source: Declaration = new_graph.getEdgeSource(e) + val target: Declaration = new_graph.getEdgeTarget(e) + if (final_graph.containsVertex(source) && final_graph.containsVertex(target)) { + final_graph.addEdge(source, target, e) + } + }) + final_graph + } + + case If(cond, thn, els) => { + val cond_variables = getVarsFromExpr(graph, cond) + val new_graph = copyGraph(graph) + + val new_graph_for_els: Graph[Declaration,DefaultEdge] = copyGraph(new_graph) + val thn_graph = compute_graph(new_graph, thn) + val els_graph = compute_graph(new_graph_for_els, els) + + //println("then graph: ", createDOT(thn_graph)) + //println("else graph: ", createDOT(els_graph)) + + + /** take union of these two graphs from the else and the then block + * First: make copy of thn_graph and declare this as the graph_union + * Second: add all edges from els_graph that are not in the graph_union yet */ + var graph_union: Graph[Declaration,DefaultEdge] = copyGraph(thn_graph) + els_graph.edgeSet().forEach(e => { + if (!graph_union.containsEdge(e)) { + graph_union.addEdge(els_graph.getEdgeSource(e), els_graph.getEdgeTarget(e), e) + } + }) + + cond_variables.foreach(src => { + val writtenToThn = writtenTo(new_graph, thn) + val writtenToEls = writtenTo(new_graph, els) + val allWrittenTo = writtenToThn.getOrElse(Set()) ++ writtenToEls.getOrElse(Set()) + allWrittenTo.foreach(t => { + /** otherwise it is an assigment to a variable that is only inside + * the scope of the block and therefore not relevant for us + */ + if (graph_union.containsVertex(t)) { + /** we need here transitive edge since otherwise the invariant may not hold (graph is transitively closed) */ + graph_union = addTransitiveEdge(graph_union, src, t) + // graph_union.addEdge(src, t, new DefaultEdge) + } + }) + }) + graph_union + } + + case w@While(cond, _, body) => { + val graph_copy : Graph[Declaration,DefaultEdge] = copyGraph(graph) + /** analyse one iteration of the while loop */ + val new_graph: Graph[Declaration, DefaultEdge] = compute_graph(graph_copy, If(cond,body,Seqn(Seq(), Seq())(body.pos))(body.pos)) + //println("graph") + //println(createDOT(graph)) + println("new_graph") + println(createDOT(new_graph)) + + /** check whether the edges are equal. + * First check whether both edge sets have the same size + * then go through each edge and check whether it also exists in the new graph*/ + var edges_equal: Boolean = graph.edgeSet().size().equals(new_graph.edgeSet().size()) + if (edges_equal) { + for (e1: DefaultEdge <- new_graph.edgeSet().asScala.toSet) { + edges_equal = false + for (e2: DefaultEdge <- graph.edgeSet().asScala.toSet) { + edges_equal = edges_equal || e1.equals(e2) + } + /** if no equal edge found then break out of the loop */ + if (!edges_equal) { + break() + } + } + } + if (new_graph.vertexSet().equals(graph.vertexSet()) && edges_equal) { + graph + } else { + compute_graph(new_graph, w) + } + } + + case LocalVarAssign(lhs, rhs) => { + val rhs_vars = getVarsFromExpr(graph, rhs) + //var lhs_decl: Declaration = LocalVarDecl("",Int)() + /** This way the position is the location of the assignment not the declaration. Better for error message but makes less sense I guess */ + val lhs_decl: Declaration = LocalVarDecl(lhs.name,lhs.typ)(lhs.pos) + //graph.vertexSet().forEach(v => if (v.name == lhs.name) {lhs_decl=v}) + //println("Before assignment: ", createDOT(graph)) + var new_graph: Graph[Declaration, DefaultEdge] = copyGraph(graph) + + val incomingEdges: Set[DefaultEdge] = graph.incomingEdgesOf(lhs_decl).asScala.toSet + + val edgesToRemove: Set[DefaultEdge] = graph.edgesOf(lhs_decl).asScala.toSet + + //println("Edges to remove", lhs, rhs, edgesToRemove) + /** remove all edges to and from the lhs in the new graph, since the lhs is reassigned */ + //println("before removing: New graph, ", createDOT(new_graph)) + edgesToRemove.foreach(e => new_graph.removeEdge(e)) + //println("after removing: New graph, ", createDOT(new_graph)) + + + /** add all new edges to the graph */ + rhs_vars.foreach(v => { + /** if v not equal to the lhs then add the Transitive edge to the new_graph */ + if(!v.equals(lhs_decl)) { + new_graph = addTransitiveEdge(new_graph, v, lhs_decl) + + /** if v is equal to the lhs then we need to add all the incoming edge to the lhs back into the new graph */ + } else { + incomingEdges.foreach(e => new_graph.addEdge(graph.getEdgeSource(e), graph.getEdgeTarget(e),e)) + } + //println("after adding the new edges: ",createDOT(new_graph)) + }) + //println("After assignment: ", createDOT(new_graph)) + + new_graph + } + + case i@Inhale(exp) => { + if (exp.isPure) { + graph + } else { + reportErrorWithMsg(ConsistencyError("There might be an impure inhale expression inside universal introduction block", i.pos)) + graph + } + } + + case Assume(_) => { + graph + } + + case Label(_, _) => { + graph + } + + /** TODO: Method call */ + case m@MethodCall(methodName, args, targets) => { + verifier.findMethod + reportErrorWithMsg(ConsistencyError("Might be not allowed method call inside universal introduction block", m.pos)) + /** maybe add to graph all edges from args to targets */ + /** somehow would have to check the influenced _ target _ by {...}. maybe like this?*/ + /* + for (s<-m.subnodes) { + if (s.isInstanceOf[PostconditionBlock]) { + + } + } + + */ + graph + } + + case f@FieldAssign(_, _) => { + reportErrorWithMsg(ConsistencyError("FieldAssign for universal introduction block", f.pos)) + graph + } + + case _ => { + throw new UnsupportedOperationException("undefined statement for universal introduction block") + } + + } + } + + def writtenTo(graph: Graph[Declaration, DefaultEdge], stmt: Stmt): Option[Set[Declaration]] = { + var output: Option[Set[Declaration]] = None + stmt match { + case Seqn(ss, _) => { + for (s <- ss) { + output match { + case None => output = writtenTo(graph,s) + case Some(v) => output = Some(v ++ writtenTo(graph,s).getOrElse(Set[Declaration]())) + } + + } + output + } + case LocalVarAssign(lhs, _) => { + // val lhs_var = LocalVarDecl(lhs.name, lhs.typ)(lhs.pos) + var lhs_decl: Declaration = LocalVarDecl("", Int)() + graph.vertexSet().forEach(v => { if (v.name == lhs.name) { lhs_decl = v } }) + Some(Set(lhs_decl)) + } + case If(_, thn, els) => { + val writtenThn: Option[Set[Declaration]] = writtenTo(graph, thn) + val writtenEls: Option[Set[Declaration]] = writtenTo(graph, els) + (writtenThn, writtenEls) match { + case (None, None) => None + case (Some(_), None) => writtenThn + case (None, Some(_)) => writtenEls + case (Some(t), Some(e)) => Some(t ++ e) + } + } + + case While(_, _, body) => { + writtenTo(graph, body) + } + + /** TODO */ + case MethodCall(_, _, _) => { + None + } + + + case Inhale(_) => { + None + } + + case Assume(_) => { + None + } + + case Label(_, _) => { + None + } + + case _ => { + None + } + } + } + /** Own Edge class such that we can define the equals method. + * Two edges should be equal if their source and target vertices are equal. */ + class DefaultEdge extends DefaultEdge { + override def equals(any: Any): Boolean = { + if(any.isInstanceOf[DefaultEdge]) { + val other = any.asInstanceOf[DefaultEdge] + this.getSource.equals(other.getSource) && this.getTarget.equals(other.getTarget) + } else { + false + } + } + } + +} + + */ \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala new file mode 100644 index 000000000..d15cd164e --- /dev/null +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala @@ -0,0 +1,415 @@ +package viper.silver.plugin.standard.reasoning.analysis + +import viper.silver.ast.{Assume, BinExp, Declaration, Exp, FieldAssign, If, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Seqn, Stmt, UnExp, While} +import viper.silver.verifier.{AbstractError, ConsistencyError} + +import scala.collection.mutable + +trait VarAnalysisSet { + + def reportErrorWithMsg(error: AbstractError): Unit + + /** + * check which arguments are influenced by universal introduction variables and add them to the tainted set. + * @param tainted + * @param stmt + * @return + */ + def get_tainted_vars_stmt(tainted: Set[Declaration], stmt: Stmt): Set[Declaration] = { + var output: Set[Declaration] = tainted + stmt match { + case Seqn(ss, decls) => { + for (s <- ss) { + output = output ++ get_tainted_vars_stmt(output, s) + } + output + } + case LocalVarAssign(lhs, rhs) => + if (is_expr_tainted(tainted, rhs)) { + tainted ++ Set(LocalVarDecl(lhs.name, lhs.typ)(stmt.pos)) + } else { + tainted + } + + case If(cond, thn, els) => { + if (is_expr_tainted(tainted, cond)) { + val written_vars_thn: Option[Set[LocalVarDecl]] = writtenTo(thn) + val written_vars_els: Option[Set[LocalVarDecl]] = writtenTo(els) + Set[Declaration]() ++ written_vars_thn.getOrElse(mutable.Set()) ++ written_vars_els.getOrElse(mutable.Set()) + } else { + get_tainted_vars_stmt(tainted, thn) ++ get_tainted_vars_stmt(tainted, els) + } + } + + case w@While(cond, _, body) => { + /* + if (is_expr_tainted(tainted, cond)) { + writtenTo(body).getOrElse(Set()) ++ Set[Declaration]() + } else { + get_tainted_vars_stmt(tainted, body) + } + */ + val new_tainted = get_tainted_vars_stmt(tainted, If(cond,body,Seqn(Seq(), Seq())(body.pos))(body.pos)) + if (new_tainted.equals(tainted)) { + tainted + } else { + get_tainted_vars_stmt(new_tainted, w) + } + } + + case m@MethodCall(_, _, _) => { + //Problem cannot reportError inside this trait! + reportErrorWithMsg(ConsistencyError("Might be not allowed method call inside universal introduction block", m.pos)) + tainted // TODO: what needs to be returned here? + } + + case i@Inhale(exp) => { + if(exp.isPure) { + tainted + } else { + reportErrorWithMsg(ConsistencyError("There might be an impure inhale expression inside universal introduction block", i.pos)) + tainted //What needs to be returned here + } + } + + case Assume(_) => { + tainted + } + + case Label(_, _) => { + tainted + } + + /** TODO: Do not allow Heap assignments */ + case f@FieldAssign(lhs, rhs) => { + reportErrorWithMsg(ConsistencyError("FieldAssign for universal introduction block", f.pos)) + tainted // TODO: what needs to be returned here ? + } + + case _ => { + throw new UnsupportedOperationException("undefined statement for universal introduction block") + } + } + } + + /* + def checkStmt(tainted: mutable.Set[Declaration], stmt: Stmt, allTainted: Boolean) : mutable.Set[Declaration] = { + stmt match { + case Seqn(ss, _) => { + for (s <- ss) { + tainted ++= checkStmt(tainted, s, allTainted) + } + tainted + } + case LocalVarAssign(lhs, rhs) => { + if (allTainted || checkExpr(tainted, rhs)) { + tainted += LocalVarDecl(lhs.name, lhs.typ)(stmt.pos) + } + tainted + } + case If(cond, thn, els) => { + if(checkExpr(tainted,cond)){ + val new_tainted: mutable.Set[Declaration] = checkStmt(tainted, thn, true) + tainted ++= checkStmt(new_tainted, els,true) + } else { + val new_tainted: mutable.Set[Declaration] = checkStmt(tainted, thn, allTainted) + tainted ++= checkStmt(new_tainted, els, allTainted) + } + tainted + } + + case While(cond, _, body) => { + if (checkExpr(tainted, cond)) { + tainted ++= checkStmt(tainted, body, true) + } else { + tainted ++= checkStmt(tainted, body, allTainted) + } + tainted + } + + case MethodCall(_, _, _) => { + //Problem cannot reportError inside this trait! + throw new IllegalArgumentException("Method call universal introduction") + } + + case Inhale(exp) => { + tainted ++= addExprToTainted(tainted, exp) + tainted + } + + case Assume(_) => { + tainted + } + + case Label(_, _) => { + tainted + } + + case _ => { + throw new UnsupportedOperationException("undefined statement for universal introduction block") + } + } + } + + */ + + /** + * expressions that should be added to tainted (e.g. for instance for inhale statements + * @param tainted + * @param exp + * @return + */ + def addExprToTainted(tainted: Set[Declaration], exp: Exp) : Set[Declaration] = { + exp match { + case l@LocalVar(_, _) => { + tainted ++ Set(LocalVarDecl(l.name, l.typ)(exp.pos)) + } + case BinExp(exp1, exp2) => { + addExprToTainted(tainted, exp1) ++ addExprToTainted(tainted, exp2) + } + case UnExp(exp) => { + addExprToTainted(tainted, exp) + } + case _ => tainted + } + } + + /** + * check whether expression contains a tainted variable + * @param tainted + * @param exp + * @return + */ + def is_expr_tainted(tainted:Set[Declaration], exp:Exp) : Boolean = { + exp match { + case l@LocalVar(_, _) => { + isTainted(LocalVarDecl(l.name, l.typ)(l.pos), tainted) + } + case BinExp(exp1, exp2) => { + is_expr_tainted(tainted, exp1) || is_expr_tainted(tainted, exp2) + } + case UnExp(exp) => { + is_expr_tainted(tainted, exp) + } + case _ => false + } + } + + def isTainted(name:Declaration, tainted:Set[Declaration]) : Boolean = { + tainted.contains(name) + } + + /** + * return variables that are assigned new values + * @param stmt + * @return + */ + def writtenTo(stmt: Stmt): Option[Set[LocalVarDecl]] = { + var output: Option[Set[LocalVarDecl]] = None + stmt match { + case Seqn(ss, _) => { + for (s <- ss) { + output match { + case None => output = writtenTo(s) + case Some(v) => output = Some(v ++ writtenTo(s).getOrElse(Set[LocalVarDecl]())) + } + + } + output + } + case LocalVarAssign(lhs, _) => { + val lhs_var = LocalVarDecl(lhs.name, lhs.typ)(lhs.pos) + Some(Set(lhs_var)) + } + case If(_, thn, els) => { + val writtenThn: Option[Set[LocalVarDecl]] = writtenTo(thn) + val writtenEls: Option[Set[LocalVarDecl]] = writtenTo(els) + (writtenThn, writtenEls) match { + case (None,None) => None + case (Some(_), None) => writtenThn + case (None, Some(_)) => writtenEls + case (Some(t), Some(e)) => Some(t++e) + } + } + + case While(_, _, body) => { + writtenTo(body) + } + + /** TODO */ + case MethodCall(_, _, _) => { + None + } + + + case Inhale(_) => { + None + } + + case Assume(_) => { + None + } + + case Label(_, _) => { + None + } + + case _ => { + None + } + } + } + + /* + + /** + * check that universal introduction variables aren't changed. + * True if immutable variables are reassigned false otherwise + */ + + // change this so it is called written (or writtenTo) + // writtenTo(stmt:Stmt) : optionalSet[LocalVarDecl] {} + def check_is_reassigned(vars: Seq[LocalVarDecl], stmt: Stmt): Seq[LocalVarDecl] = { + var output = Seq[LocalVarDecl]() + stmt match { + case Seqn(ss, _) => { + for (s <- ss) { + output = output ++ check_is_reassigned(vars, s) + } + output + } + case LocalVarAssign(lhs, _) => { + var output = Seq[LocalVarDecl]() + val lhs_var = LocalVarDecl(lhs.name, lhs.typ)(lhs.pos) + if (vars.contains(lhs_var)) { + output ++= Seq(lhs_var) + } + output + } + case If(_, thn, els) => { + check_is_reassigned(vars, thn) ++ check_is_reassigned(vars, els) + } + + case While(_, _, body) => { + check_is_reassigned(vars, body) + } + + /** return false for now if immutable vars are part of arguments of method call */ + case MethodCall(_, args, _) => { + var output = Seq[LocalVarDecl]() + for (exp <- args) { + output = output ++ expr_contains_immutable(vars, exp) + } + output + } + + + case Inhale(_) => { + Seq[LocalVarDecl]() + } + + case Assume(_) => { + Seq[LocalVarDecl]() + } + + case Label(_, _) => { + Seq[LocalVarDecl]() + } + + case _ => { + Seq[LocalVarDecl]() + } + } + } + + def expr_contains_immutable(vars: Seq[LocalVarDecl], exp: Exp): Seq[LocalVarDecl] = { + exp match { + case l@LocalVar(_, _) => { + val v = LocalVarDecl(l.name, l.typ)(l.pos) + if(vars.contains(v)){ + Seq(v) + } else { + Seq() + } + } + case BinExp(exp1, exp2) => { + expr_contains_immutable(vars, exp1) ++ expr_contains_immutable(vars, exp2) + } + case UnExp(exp) => { + expr_contains_immutable(vars, exp) + } + case _ => Seq() + } + } + + */ + + /* + + def check_is_reassigned_bool(vars: Seq[LocalVarDecl], stmt: Stmt) : Boolean = { + var output = false + stmt match { + case Seqn(ss, _) => { + for (s <- ss) { + output = check_is_reassigned(vars, s) + } + output + } + case LocalVarAssign(lhs, _) => { + var output: Boolean = false + if (vars.contains(LocalVarDecl(lhs.name, lhs.typ)(lhs.pos))) { + output = true + } + output + } + case If(_, thn, els) => { + check_is_reassigned(vars, thn) || check_is_reassigned(vars, els) + } + + case While(_, _, body) => { + check_is_reassigned(vars, body) + } + + /** return false for now if immutable vars are part of arguments of method call */ + case MethodCall(_, args, _) => { + var output: Boolean = false + for (exp <- args) { + output = output || expr_contains_immutable(vars, exp) + } + output + } + + + case Inhale(_) => { + false + } + + case Assume(_) => { + false + } + + case Label(_, _) => { + false + } + + case _ => { + false + } + } + } + + def expr_contains_immutable_bool(vars: Seq[LocalVarDecl], exp: Exp): Boolean = { + exp match { + case l@LocalVar(_, _) => { + vars.contains(LocalVarDecl(l.name, l.typ)(l.pos)) + } + case BinExp(exp1, exp2) => { + expr_contains_immutable(vars, exp1) || expr_contains_immutable(vars, exp2) + } + case UnExp(exp) => { + expr_contains_immutable(vars, exp) + } + case _ => false + } + }*/ +} diff --git a/src/test/resources/reasoning/existential_elim_impure.vpr b/src/test/resources/reasoning/existential_elim_impure.vpr index 748df03b4..b88d51ab9 100644 --- a/src/test/resources/reasoning/existential_elim_impure.vpr +++ b/src/test/resources/reasoning/existential_elim_impure.vpr @@ -3,7 +3,7 @@ field f: Int method ex2() { + //:: ExpectedOutput(consistency.error) obtain x: Ref where {x.f} acc(x.f) //assert exists x: Ref :: acc(x.f) - //:: ExpectedOutput(consistency.error) } \ No newline at end of file diff --git a/src/test/resources/reasoning/immutableVar.vpr b/src/test/resources/reasoning/immutableVar.vpr new file mode 100644 index 000000000..c7805ab1e --- /dev/null +++ b/src/test/resources/reasoning/immutableVar.vpr @@ -0,0 +1,71 @@ +function P(x: Int) : Bool { + x == 0 +} + +function Q(x: Int) : Bool { + x == 0 +} + +method xassigned() +{ + var z:Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var y:Int := x+1 + x:=2 // x should be immutable + } +} + +method xwhile() +{ + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + while (x<0) { + x := x+1 + } + } +} + +method xif() +{ + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var y: Int := 2 + if (y==2) { + y:= y-1 + } else { + x := x+1 + } + } +} + +method dosomething(x: Int) +{ + var y:Int := x +} + +method xmcall() +{ + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + // could influence the heap + + dosomething(x) + } +} + +method xmultOK() +{ + prove forall x:Int, y:Int {P(x),P(y)} assuming P(x) implies Q(x) { + var z: Int := 5 + } +} + +method xmult() +{ + //:: ExpectedOutput(consistency.error) + prove forall x:Int, y:Int {P(x),P(y)} assuming P(x) implies Q(x) { + x:=y + y:=3 + } +} \ No newline at end of file diff --git a/src/test/resources/reasoning/outsideBlockAssignmentUnivIntro.vpr b/src/test/resources/reasoning/outsideBlockAssignmentUnivIntro.vpr new file mode 100644 index 000000000..4511f7767 --- /dev/null +++ b/src/test/resources/reasoning/outsideBlockAssignmentUnivIntro.vpr @@ -0,0 +1,347 @@ +function P(x: Int) : Bool { + x == 0 +} + +function Q(x: Int) : Bool { + x == 0 +} + +method m0() +{ + var z:Int := 0 + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var y:Int := x+1 + } + if (true) { var y:Int } // this should work -> two separate blocks + // var y:Int := 0 //here duplicate identifier +} +method mIndirect() +{ + var z:Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var y:Int := x+1 + z := y // Consistency error + } + // problem if here var y:Int := 0 -> this will also be in tainted set + if (true) { var y:Int } +} + +method mIfCnd() +{ + var z: Int := 0 + var y: Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + if(x>5) { + z := 3 + } else { + y := 5 + } + } +} + +method mIfNOK() +{ + var w :Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var l :Int := 0 + if (l >= -1) { + l := x + } else { + w := x + } + } +} + +method mIfOK1() +{ + var w :Int := 0 + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var l :Int := 0 + var g: Int := 0 + if (l >= -1) { + g := x + } else { + w := 4 + } + } +} + +method mIfOK2() +{ + var w :Int := 0 + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var l :Int := 0 + if (l >= -1) { + l := x + } else { + w := 4 // should be SAFE + } + } +} + +method mWhileCnd() +{ + var z: Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + while(x>5) { + z := z+3 + } + } +} + +method mWhileOK() +{ + var z: Int := 0 + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var y: Int := 0 + while(y<5) { + z := z+3 + y := y+1 + } + } +} + +method mWhileOK2() +{ + var z: Int := 0 + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + var y: Int := 0 + while(y<100) { // will only be tainted after the 5th? iteration + var x4: Int := 0 + var x3: Int := 0 + var x2: Int := 0 + var x1: Int := 0 + z := x4 + x4 := x3 + x3 := x2 + x2 := x1 + x1 := x + y := y+1 + } + } +} + +method mWhileNOK() +{ + var z: Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + var y: Int := 0 + var x2: Int := 0 + var x1: Int := 0 + + while(y<100) { // will only be tainted after the 5th? iteration + z := x2 + x2 := x1 + x1 := x + y := y+1 + } + } +} + +method mWhileNOK2() +{ + var z: Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + var y: Int := x + var x2: Int := 0 + var x1: Int := 0 + + while(y<100) { // will only be tainted after the 5th? iteration + z := x2 + x2 := x1 + y := y+1 + } + } +} + +method mcall() +{ + var y: Int := 0 + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + mWhileOK() + } +} + +method example1(x:Int, y:Int) returns (res: Int) +influenced res by {x, y} +{ + res := x-y +} + +method mMethodCallNOK1() +{ + var z: Int + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + z := example1(x,x) + } +} + +//code without influenced by statement +method example2(x:Int, y:Int) returns (res: Int) +{ + res := 0 +} + +method mMethodCallNOK2() +{ + var z: Int + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + z := example2(x,x) + } +} + +//code with influenced by statement +method example3(x:Int, y:Int) returns (res: Int) +influenced res by {} +{ + res := 0 +} + +method mMethodCallOK1() +{ + var z: Int + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var y:Int := 3 + z := example3(y,y) + } +} + +//method with several return values +method exampleMult(x:Int, y:Int) returns (diff:Int, id:Int) +influenced diff by {x, y} +influenced id by {x} +{ + diff := x-y + id := x +} + +method mMethodCallNOK3() +{ + var z: Int + var w: Int + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var y: Int := 3 + z,w := exampleMult(y,x) + } +} + +method exampleIncomplete(b:Bool,c:Int) returns (z:Int, w:Int) +influenced w by {b} +{ + z := 3 + if (b) { + w := w + 1 + } +} + +method mMethodCallOK2() +{ + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var y:Int + var v:Int + var bool:Bool := true + var count: Int := 16 + y,v := exampleIncomplete(bool,count) + } +} + + +method mAssume() +{ + var m: Int := 0 + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + assume(m==0) + } +} + +method mInhalingOK() +{ + var m: Int := 0 + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + var y: Int := 0 + inhale (y==0) + } +} + +field f: Int + +method mInhaleOK2(y: Ref) + +{ + + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + inhale (acc(y.f)) + } +} + +method mExhaleOK(y:Ref) +requires acc(y.f) +{ + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + exhale acc(y.f) + } +} + + +method mFieldAssignNOK1(y:Ref) +requires acc(y.f) +{ + //:: ExpectedOutput(consistency.error) + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + y.f := x + } +} + +method mFieldAssignNOK2(y:Ref) +{ + var w: Int + //:: ExpectedOutput(consistency.error) + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + var z: Int := x + w := z + } +} + + +method mFieldAssignOK1(y:Ref) +requires acc(y.f) +{ + var z: Int := 0 + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + z := y.f + } +} + + +function func1(x:Int) : Int +{ + x +} + +method mFunctionOK() +{ + var z: Int := 3 + var y: Int + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + y := func1(z) + } +} + +method mFunctionNOK() +{ + var z: Int := 3 + var y: Int + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + y := func1(x) + } +} diff --git a/src/test/resources/reasoning/test.vpr b/src/test/resources/reasoning/test.vpr new file mode 100644 index 000000000..7e338eece --- /dev/null +++ b/src/test/resources/reasoning/test.vpr @@ -0,0 +1,330 @@ +function P(x: Int) : Bool { + x == 0 +} + +function Q(x: Int) : Bool { + x == 0 +} +field f: Int + + +method simple() +{ + var z: Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + z := x + } +} + + +method mIfOK1() +{ + var w :Int := 0 + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var l :Int := 0 + var g: Int := 0 + if (l >= -1) { + g := x + } else { + w := 4 + } + } +} + + + +method mWhileOK() +{ + var z: Int := 0 + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + var y: Int := 0 + while(y<100) { // will only be tainted after the 5th? iteration + var x4: Int := 0 + var x3: Int := 0 + var x2: Int := 0 + var x1: Int := 0 + z := x4 + x4 := x3 + x3 := x2 + x2 := x1 + x1 := x + y := y+1 + } + } +} + + +method mWhileNOK() +{ + var z: Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + var y: Int := 0 + var x2: Int := 0 + var x1: Int := 0 + + while(y<100) { // will only be tainted after the 5th? iteration + z := x2 + x2 := x1 + x1 := x + y := y+1 + } + } +} + + +//graph at the end such that z is influenced by .init_z and .init_x +//correct because if loop not executed then z is influenced by .init_z? +method mWhileNOK2() +{ + var z: Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + var y: Int := x + var x2: Int := 0 + var x1: Int := 0 + + while(y<100) { + z := x2 + x2 := x1 + y := y+1 + } + } +} + + + + +method mFieldAssignNOK1(y:Ref) +requires acc(y.f) +{ + //:: ExpectedOutput(consistency.error) + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + y.f := x + } +} + +method mFieldAssignNOK2(y:Ref) +{ + var w: Int + //:: ExpectedOutput(consistency.error) + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + var z: Int := x + w := z + } +} + + +method mFieldAssignOK1(y:Ref) +requires acc(y.f) +{ + var z: Int := 0 + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + z := y.f + } +} + + +method mInhaleOK2(y: Ref) + +{ + + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + inhale (acc(y.f)) + } +} + + + +method mExhaleOK(y:Ref) +requires acc(y.f) +{ + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + exhale acc(y.f) + } +} + + +function func1(x:Int) : Int +{ + x +} + +method mFunctionOK() +{ + var z: Int := 3 + var y: Int + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + y := func1(z) + } +} + +method mFunctionNOK() +{ + var z: Int := 3 + var y: Int + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + y := func1(x) + } +} + + + +method example1(x:Int, y:Int) returns (res: Int) +influenced res by {x, y} +{ + res := x-y +} + +method mMethodCallNOK1() +{ + var z: Int + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + z := example1(x,x) + } +} + +//code without influenced by statement +method example2(x:Int, y:Int) returns (res: Int) +{ + res := 0 +} + +method mMethodCallNOK2() +{ + var z: Int + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + z := example2(x,x) + } +} + +//code with influenced by statement +method example3(x:Int, y:Int) returns (res: Int) +influenced res by {} +{ + res := 0 +} + +method mMethodCallOK1() +{ + var z: Int + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var y:Int := 3 + z := example3(y,y) + } +} + +//method with several return values +method exampleMult(a0:Int, a1:Int) returns (r0:Int, r1:Int) +influenced r0 by {a0, a1} +influenced r1 by {a0} +{ + r0 := a0-a1 + r1 := a0 +} + +method mMethodCallNOK3() +{ + var z: Int + var w: Int + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var v: Int := 3 + z,w := exampleMult(v,x) + } +} + + +method exampleIncomplete(b:Bool,c:Int) returns (z:Int, w:Int) +influenced w by {b} +{ + z := 3 + if (b) { + var y: Int := 2 + w := y + 1 + } +} + +method mMethodCallOK2() +{ + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var y:Int + var v:Int + var bool:Bool := true + var count: Int := 16 + y,v := exampleIncomplete(bool,count) + } +} + +method exampleHeap(b:Int) returns (c:Int) +influenced heap by {heap} +influenced c by {} +{ + c := 3 +} + + +method mMethodCallOK3() +{ + var z: Int + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + z := exampleHeap(x) + } +} + + +//:: ExpectedOutput(consistency.error) +method exampleincorrect(b:Bool,c:Int) returns (z:Int, w:Int) +influenced w by {} +{ + z := 3 + if (b) { + var y: Int := 2 + w := y + 1 + } +} + +method exampleOverapprox(b:Bool,c:Int) returns (z:Int, w:Int) +influenced w by {b,c} +{ + z := 3 + if (b) { + var y: Int := 2 + w := y + 1 + } +} + + + + +method exampleHeapArg2(b:Ref) returns (c:Int) +requires acc(b.f) +influenced heap by { heap } +influenced c by { heap } +{ + c := b.f +} + +method exampleHeapArg3(b:Ref) returns (c:Int) +requires acc(b.f) +influenced heap by { b, heap } +influenced c by { b, heap } +{ + c := b.f +} + +method exampleHeapArg4(b:Ref) returns (c:Int) +requires acc(b.f) +influenced heap by { heap, b } +influenced c by { heap, b } +{ + c := b.f +} + + + + diff --git a/src/test/resources/reasoning/universal_intro_assuming_false.vpr b/src/test/resources/reasoning/universal_intro_assuming_false.vpr new file mode 100644 index 000000000..e4596d534 --- /dev/null +++ b/src/test/resources/reasoning/universal_intro_assuming_false.vpr @@ -0,0 +1,19 @@ +function P(k: Int) : Bool +{ + false +} + +function Q(k: Int) : Bool +{ + k==2 +} + +method m1() +{ + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var y:Int := x+1 + } +} + + + diff --git a/src/test/resources/reasoning/universal_intro_simple.vpr b/src/test/resources/reasoning/universal_intro_simple.vpr index 38c893d19..5d1235c83 100644 --- a/src/test/resources/reasoning/universal_intro_simple.vpr +++ b/src/test/resources/reasoning/universal_intro_simple.vpr @@ -1,7 +1,30 @@ +function greaterthanzero(x:Int) :Bool +{ + x > 0 +} + method ex1() { var y: Int - prove forall x:Int {x>0} assuming x>0 implies x>1 { - x:=x+1 + prove forall x:Int {greaterthanzero(x)} assuming x>0 implies x>0 { + var z: Int := x+1 + } +} + + + +function g_zero(x:Int) : Bool { + x>0 +} +function greater(x:Int, y:Int) :Bool { + x>y +} +method ex2() +{ + var i:Int := 10 + var j:Int := 5 + prove forall x:Int, y:Int {g_zero(y),greater(x,y)} assuming (g_zero(y) && greater(x,y)) implies g_zero(x) { + var z: Bool := x>y } -} \ No newline at end of file + assert greater(i,j) +} From 76ed0cd2b9adf40e8ed7e5f5eec41eb84b84f4c4 Mon Sep 17 00:00:00 2001 From: Dina Weiersmueller Date: Sat, 25 Feb 2023 12:04:51 +0100 Subject: [PATCH 06/41] oldCall added, restructured Code --- .../reasoning/BeforeVerifyHelper.scala | 209 +++++++++++ .../reasoning/ReasoningASTExtension.scala | 124 ++++++- .../reasoning/ReasoningPASTExtension.scala | 121 ++++++- .../standard/reasoning/ReasoningPlugin.scala | 338 ++++++------------ .../reasoning/analysis/VarAnalysisGraph.scala | 183 +++++++--- .../reasoning/analysis/VarAnalysisSet.scala | 20 ++ src/test/resources/reasoning/test.vpr | 83 ++++- 7 files changed, 781 insertions(+), 297 deletions(-) create mode 100644 src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala new file mode 100644 index 000000000..f846de1fd --- /dev/null +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala @@ -0,0 +1,209 @@ +package viper.silver.plugin.standard.reasoning + +import org.jgrapht.graph.DefaultEdge +import viper.silver.ast.utility.Expressions +import viper.silver.ast.{Apply, Exhale, Exp, FieldAssign, Fold, Inhale, LocalVar, LocalVarDecl, Method, MethodCall, Package, Position, Program, Seqn, Stmt, Unfold} +import viper.silver.plugin.standard.reasoning.analysis.VarAnalysisGraph +import viper.silver.plugin.standard.termination.{DecreasesSpecification, DecreasesTuple, DecreasesWildcard} +import viper.silver.verifier.{AbstractError, ConsistencyError} + +import scala.collection.mutable +import scala.jdk.CollectionConverters.CollectionHasAsScala + +trait BeforeVerifyHelper { + + + + /** methods to rename variables */ + def uniqueName(name: String, usedNames: mutable.Set[String]): String = { + println("usedNames: " + usedNames) + var i = 1 + var newName = name + while (usedNames.contains(newName)) { + newName = name + i + i += 1 + } + usedNames.add(newName) + newName + } + + def substituteWithFreshVars[E <: Exp](vars: Seq[LocalVarDecl], exp: E, usedNames: mutable.Set[String]): (Seq[(LocalVarDecl, LocalVarDecl)], E) = { + val declMapping = vars.map(oldDecl => + oldDecl -> LocalVarDecl(uniqueName(oldDecl.name, usedNames), oldDecl.typ)(oldDecl.pos, oldDecl.info, oldDecl.errT)) + val transformedExp = applySubstitution(declMapping, exp) + (declMapping, transformedExp) + } + + def applySubstitution[E <: Exp](mapping: Seq[(LocalVarDecl, LocalVarDecl)], exp: E): E = { + Expressions.instantiateVariables(exp, mapping.map(_._1.localVar), mapping.map(_._2.localVar)) + } + + /** + * get all variables that are assigned to inside the block and take intersection with universal introduction + * variables. If they are contained throw error since these variables should be immutable + */ + def checkReassigned(written_vars: Option[Set[LocalVarDecl]], v: Seq[LocalVarDecl], reportError: AbstractError => Unit, u: UniversalIntro): Unit = { + if (written_vars.isDefined) { + val reassigned_vars: Set[LocalVarDecl] = written_vars.get.intersect(v.toSet) + if (reassigned_vars.nonEmpty) { + val reassigned_names: String = reassigned_vars.mkString(", ") + val reassigned_pos: String = reassigned_vars.map(_.pos).mkString(", ") + reportError(ConsistencyError("Universal Introduction variable(s) (" + reassigned_names + ") might have been reassigned at position(s) (" + reassigned_pos + ")", u.pos)) + } + } + } + + + /** check if isLemma precondition is correct */ + def checkLemma(input: Program, reportError: AbstractError => Unit) = { + input.methods.foreach(method => { + var containsLemma: Boolean = method.pres.exists(p => p.isInstanceOf[Lemma]) + containsLemma = (containsLemma || method.posts.exists(p => p.isInstanceOf[Lemma])) + var containsDecreases = false + if (containsLemma) { + /** check preconditions for decreases clause */ + method.pres.foreach { + case DecreasesTuple(_, _) => + containsDecreases = true + case DecreasesWildcard(_) => + containsDecreases = true + case s@_ => + () + } + /** check postconditions for decreases clause */ + method.posts.foreach { + case DecreasesTuple(_, _) => + containsDecreases = true + case DecreasesWildcard(_) => + containsDecreases = true + case s@_ => + () + } + + /** check info for decreases specification */ + method.meta._2 match { + case spec: DecreasesSpecification => + if (spec.star.isDefined) { + reportError(ConsistencyError("decreases statement might not prove termination", method.pos)) + } else { + containsDecreases = true + } + case _ => + } + + + } + + /** report error if there is no decreases clause or specification */ + if (containsLemma && !containsDecreases) { + reportError(ConsistencyError(s"method ${method.name} marked lemma might not contain decreases clause", method.pos)) + } + + /** check method body for impure statements */ + if (containsLemma) { + checkBodyPure(method.body.getOrElse(Seqn(Seq(), Seq())()), method, input, reportError) + } + + }) + + } + + /** checks whether the body is pure, reports Error if impure operation found */ + def checkBodyPure(stmt: Stmt, method: Method, prog: Program, reportError: AbstractError => Unit): Boolean = { + var pure: Boolean = true + stmt match { + case Seqn(ss, _) => + ss.foreach(s => { + pure = pure && checkBodyPure(s, method, prog, reportError) + }) + pure + case ie@(Inhale(_) | Exhale(_) | FieldAssign(_, _) | Fold(_) | Unfold(_) | Apply(_) | Package(_, _)) => + reportError(ConsistencyError(s"method ${method.name} marked lemma might contain impure statement ${ie}", ie.pos)) + false + case m@MethodCall(methodName, _, _) => + val mc = prog.findMethod(methodName) + var containsLemma: Boolean = mc.pres.exists(p => p.isInstanceOf[Lemma]) + containsLemma = containsLemma || mc.posts.exists(p => p.isInstanceOf[Lemma]) + + /** if called method is not a lemma report error */ + if (!containsLemma) { + reportError(ConsistencyError(s"method ${method.name} marked lemma might contain call to method ${m}", m.pos)) + false + } else { + pure + } + case _ => + pure + } + } + + /** check that influenced by expressions are exact or overapproximate the body of the method. */ + def checkInfluencedBy(input: Program, reportError: AbstractError => Unit): Unit = { + val body_graph_analysis: VarAnalysisGraph = VarAnalysisGraph(input, reportError) + input.methods.foreach(method => { + var return_vars = method.formalReturns.toSet ++ Seq(body_graph_analysis.heap_vertex) + val arg_vars = method.formalArgs.toSet ++ Seq(body_graph_analysis.heap_vertex) + + /** helper variable for error message, maps local variable to its position */ + var influenced_by_pos: Map[LocalVarDecl, Position] = Map() + + val init_args_decl = method.formalArgs.map(a => body_graph_analysis.createInitialVertex(a)) + val init_rets_decl = method.formalReturns.map(r => body_graph_analysis.createInitialVertex(r)) + val method_vars: Map[LocalVarDecl, LocalVarDecl] = ((method.formalArgs zip init_args_decl) ++ (method.formalReturns zip init_rets_decl) ++ Seq((body_graph_analysis.heap_vertex, body_graph_analysis.createInitialVertex(body_graph_analysis.heap_vertex)))).toMap + val empty_body_graph = body_graph_analysis.createEmptyGraph(method_vars) + val body_graph = body_graph_analysis.compute_graph(empty_body_graph, method.body.getOrElse(Seqn(Seq(), Seq())()), method_vars) + val heap_vert = LocalVarDecl(body_graph_analysis.heap_vertex.name, body_graph_analysis.heap_vertex.typ)() + + val influenced_graph = body_graph_analysis.copyGraph(empty_body_graph) + method.posts.foreach { + case v@FlowAnnotation(target, args) => + + val target_var: LocalVar = if (target.isInstanceOf[Var]) target.asInstanceOf[Var].var_decl.asInstanceOf[LocalVar] else body_graph_analysis.heap_vertex.localVar + val target_decl: LocalVarDecl = LocalVarDecl(target_var.name, target_var.typ)(v.pos) + + if (!return_vars.contains(target_decl)) { + reportError(ConsistencyError(s"Only return variables can be influenced and only one influenced by expression per return variable can exist. ${target_decl.name} may not be a return variable or might be used several times.", v.pos)) + } + return_vars -= target_decl + influenced_by_pos += (target_decl -> v.pos) + args.foreach(arg => { + val arg_var: LocalVar = if (arg.isInstanceOf[Var]) arg.asInstanceOf[Var].var_decl.asInstanceOf[LocalVar] else body_graph_analysis.heap_vertex.localVar + val arg_decl: LocalVarDecl = LocalVarDecl(arg_var.name, arg_var.typ)(arg_var.pos) + if (!arg_vars.contains(arg_decl)) { + reportError(ConsistencyError(s"Only argument variables can be influencing the return variable. ${arg_decl.name} may not be an argument variable.", v.pos)) + } + influenced_graph.addEdge(method_vars(arg_decl), target_decl, new DefaultEdge) + }) + + } + + return_vars.foreach(rv => { + (method.formalArgs ++ Seq(body_graph_analysis.heap_vertex)).foreach(a => { + if (!influenced_graph.containsVertex(method_vars(a))) { + influenced_graph.addVertex(method_vars(a)) + } + if (!influenced_graph.containsVertex(rv)) { + influenced_graph.addVertex(rv) + } + influenced_graph.addEdge(method_vars(a), rv, new DefaultEdge) + }) + }) + + + /** ignore the edges from the .init_ret to the ret vertex since before the method there is no init value of a return variable. */ + method.formalReturns.foreach(r => { + body_graph.removeAllEdges(method_vars(r), r) + }) + + /** the set of all incoming edges to the return variables of the method body graph should be a subset of the set of the incoming edges of the influenced by graph */ + (method.formalReturns ++ Seq(heap_vert)).foreach(r => { + body_graph.incomingEdgesOf(r).forEach(e => { + if (!influenced_graph.containsEdge(body_graph.getEdgeSource(e), body_graph.getEdgeTarget(e))) { + val ret_sources: String = body_graph.incomingEdgesOf(r).asScala.map(e => body_graph.getEdgeSource(e).name).toSet.mkString(", ").replace(".init_", "").replace(".", "") + reportError(ConsistencyError("influenced by expression may be incorrect. Possible influenced by expression: \n" + "influenced " + r.name.replace(".", "") + " by {" + ret_sources + "}", influenced_by_pos(r))) + } + }) + }) + }) + } +} diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala index ca990569f..5abdb0486 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala @@ -1,9 +1,11 @@ package viper.silver.plugin.standard.reasoning +import viper.silver.FastMessaging import viper.silver.ast._ -import viper.silver.ast.pretty.FastPrettyPrinter.{ContOps, char, group, line, nil, show, showBlock, showVars, space, ssep, text, toParenDoc} +import viper.silver.ast.pretty.FastPrettyPrinter.{ContOps, braces, brackets, char, defaultIndent, group, line, lineIfSomeNonEmpty, nest, nil, parens, show, showBlock, showContracts, showStmt, showVars, space, ssep, text, toParenDoc} import viper.silver.ast.pretty.PrettyPrintPrimitives import viper.silver.ast.utility.{Consistency, Expressions} +import viper.silver.plugin.standard.termination.PDecreasesClause import viper.silver.verifier.{ConsistencyError, Failure, VerificationResult} @@ -52,8 +54,19 @@ case class UniversalIntro(varList: Seq[LocalVarDecl], triggers: Seq[Trigger], ex override val extensionSubnodes: Seq[Node] = varList ++ triggers ++ Seq(exp1) ++ Seq(exp2) ++ Seq(block) } +sealed trait FlowVar extends Node { -sealed trait FlowAnnotation extends ExtensionExp with Node with Scope { +} + +case class Heap()(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowVar { + +} + +case class Var(decl:Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowVar { + def var_decl: Exp = decl +} +case class FlowAnnotation(v: FlowVar, varList: Seq[FlowVar])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionExp with Node with Scope { +//sealed trait FlowAnnotation extends ExtensionExp with Node with Scope { override def extensionIsPure: Boolean = true override val scopedDecls = Seq() @@ -65,9 +78,27 @@ sealed trait FlowAnnotation extends ExtensionExp with Node with Scope { Failure(Seq(ConsistencyError("FlowAnalysis: verifyExtExp has not been implemented.", pos))) } -} + override def extensionSubnodes: Seq[Node] = { + if (v.isInstanceOf[Var]) (Seq(v.asInstanceOf[Var].var_decl)) else (Seq()) ++ varList.filter(vl => vl.isInstanceOf[Var]).map(vl => vl.asInstanceOf[Var].var_decl) + } + + /** Pretty printing functionality as defined for other nodes in class FastPrettyPrinter. + * Sample implementation would be text("old") <> parens(show(e)) for pretty-printing an old-expression. */ + override def prettyPrint: PrettyPrintPrimitives#Cont = { + text("influenced") <+> (v match { + case value: Var => (show(value.var_decl)) + case _ => text("heap") + }) <+> + text("by") <+> + ssep(varList.map { + case value: Var => show(value.var_decl) + case _ => text("heap") + }, group(char(',') <> line(" "))) + } +} +/* case class FlowAnnotationVar(v: Exp, varList: Seq[Exp])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowAnnotation { override def extensionSubnodes: Seq[Node] = Seq(v) ++ varList @@ -116,4 +147,91 @@ case class FlowAnnotationHeapHeapArg(varList: Seq[Exp])(val pos: Position = NoPo ssep(varList map show, group(char (',') <> line(" "))) <+> text(", heap") } +} + + */ + +case class Lemma()(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionExp with Node with Scope { + + override def extensionIsPure: Boolean = true + + override val scopedDecls = Seq() + + override def typ: Type = Bool + + override def verifyExtExp(): VerificationResult = { + assert(assertion = false, "Lemma: verifyExtExp has not been implemented.") + Failure(Seq(ConsistencyError("Lemma: verifyExtExp has not been implemented.", pos))) + } + + override def extensionSubnodes: Seq[Node] = Seq() + + /** Pretty printing functionality as defined for other nodes in class FastPrettyPrinter. + * Sample implementation would be text("old") <> parens(show(e)) for pretty-printing an old-expression. */ + override def prettyPrint: PrettyPrintPrimitives#Cont = { + text("isLemma") + + } + + /* + override def extensionSubnodes: Seq[Node] = method.subnodes + + override lazy val check: Seq[ConsistencyError] = { + + var errors: Seq[ConsistencyError] = Seq() + + var decreasesclauses = method.posts.filter(post => post.isInstanceOf[PDecreasesClause]) + decreasesclauses ++= method.pres.filter(pre => pre.isInstanceOf[PDecreasesClause]) + + if (decreasesclauses.isEmpty) { + errors ++= Seq(ConsistencyError("Lemma is not guaranteed to terminate. Add decreases clause.", this.pos)) + } + var pure = method.posts.filter(post => !post.isPure) + pure ++= method.pres.filter(pre => !pre.isPure) + if(pure.nonEmpty && method.pres.nonEmpty){ + errors ++= Seq(ConsistencyError("Lemma is not pure", this.pos)) + } + errors + } + override def prettyPrint: PrettyPrintPrimitives#Cont = { + group(text("lemma") <+> name <> nest(defaultIndent, parens(showVars(method.formalArgs))) <> { + if (method.formalReturns.isEmpty) nil + else nest(defaultIndent, line <> "returns" <+> parens(showVars(method.formalReturns))) + }) <> + nest(defaultIndent, + showContracts("requires", method.pres) <> + showContracts("ensures", method.posts) + ) <> + line <> ( + method.body match { + case None => + nil + case Some(actualBody) => + braces(nest(defaultIndent, + lineIfSomeNonEmpty(actualBody.children) <> + ssep(Seq(showStmt(actualBody)), line) + ) <> line) + }) + } + + override def name: String = method.name + + override val scopedDecls: Seq[Declaration] = method.scopedDecls + + */ +} + +case class OldCall(mc:MethodCall, l:Label)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt with Scope { + override val scopedDecls = Seq() + + + override lazy val prettyPrint: PrettyPrintPrimitives#Cont = { + val call = text("oldCall") <> brackets(show(l)) <> text(mc.methodName) <> nest(defaultIndent, parens(ssep(mc.args map show, group(char(',') <> line)))) + mc.targets match { + case Nil => call + case _ => ssep(mc.targets map show, char(',') <> space) <+> ":=" <+> call + } + } + + override val extensionSubnodes: Seq[Node] = mc.args ++ mc.targets } \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala index 855587049..991fbd837 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -1,8 +1,10 @@ package viper.silver.plugin.standard.reasoning -import viper.silver.ast.{ExtensionExp, LocalVarDecl, Position, Seqn, Stmt, Trigger} +import viper.silver.FastMessaging +import viper.silver.ast.{ErrorTrafo, ExtensionExp, Info, Label, LocalVar, LocalVarDecl, Member, Method, MethodCall, NoInfo, NoPosition, NoTrafos, Position, Seqn, SourcePosition, Stmt, Trigger} import viper.silver.parser.TypeHelper.Bool -import viper.silver.parser.{NameAnalyser, PExp, PExtender, PLocalVarDecl, PNode, PSeqn, PStmt, PTrigger, PType, PTypeSubstitution, Translator, TypeChecker} +import viper.silver.parser.{NameAnalyser, PExp, PExtender, PGlobalDeclaration, PIdnDef, PIdnUse, PLabel, PLocalVarDecl, PMember, PMethod, PNode, PSeqn, PStmt, PTrigger, PType, PTypeSubstitution, Translator, TypeChecker} +import viper.silver.plugin.standard.termination.PDecreasesClause case class PExistentialElim(varList: Seq[PLocalVarDecl], trig: Seq[PTrigger], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { override val getSubnodes: Seq[PNode] = { @@ -42,14 +44,54 @@ case class PUniversalIntro(varList: Seq[PLocalVarDecl], triggers: Seq[PTrigger], } -sealed trait PFlowAnnotation extends PExtender with PExp { +case class PFlowAnnotation(v:PFlowVar, varList: Seq[PFlowVar])(val pos: (Position,Position)) extends PExtender with PExp { +//sealed trait PFlowAnnotation extends PExtender with PExp { override def typeSubstitutions: Seq[PTypeSubstitution] = Seq(PTypeSubstitution.id) override def forceSubstitution(ts: PTypeSubstitution): Unit = {} override val getSubnodes: Seq[PNode] = Seq() + + //from here new + override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = { + + varList.foreach(c => { + if (c.isInstanceOf[PVar]) { + t.checkTopTyped(c.asInstanceOf[PVar].var_decl, None) + } + }) + if (v.isInstanceOf[PVar]) { + t.checkTopTyped(v.asInstanceOf[PVar].var_decl, None) + } + + + None + } + + override def translateExp(t: Translator): ExtensionExp = { + FlowAnnotation(v.translate(t), varList.map { case variable => variable.translate(t) })(t.liftPos(this)) + + } +} +sealed trait PFlowVar extends PNode { + + def translate(t:Translator): FlowVar +} + +case class PHeap()(val pos: (Position,Position)) extends PFlowVar { + override def translate(t:Translator): Heap = { + Heap()(t.liftPos(this)) + } } +case class PVar(decl:PExp)(val pos: (Position,Position)) extends PFlowVar{ + def var_decl:PExp = decl + + override def translate(t:Translator): Var = { + Var(t.exp(decl))(t.liftPos(this)) + } +} +/* case class PFlowAnnotationVar(v:PExp, varList: Seq[PExp])(val pos: (Position,Position)) extends PFlowAnnotation { override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = { @@ -104,4 +146,77 @@ case class PFlowAnnotationHeapHeapArg(varList: Seq[PExp])(val pos: (Position,Pos FlowAnnotationHeapHeapArg(varList.map { case variable => t.exp(variable) })(t.liftPos(this)) } +} + + */ + + +case class PLemma()(val pos: (Position,Position)) extends PExtender with PExp { + + override def typeSubstitutions: Seq[PTypeSubstitution] = Seq(PTypeSubstitution.id) + + override def forceSubstitution(ts: PTypeSubstitution): Unit = {} + + override val getSubnodes: Seq[PNode] = Seq() + + override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = { + None + } + + override def translateExp(t: Translator): ExtensionExp = { + Lemma()(t.liftPos(this)) + + } + /* + override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = { + None + } + + + override def getSubnodes(): Seq[PNode] = method.subnodes + + override def typecheck(t: TypeChecker, n: NameAnalyser): Option[Seq[String]] = { + t.checkMember(method)() + None + } + + override def idndef: PIdnDef = method.idndef + + override val pos: (Position, Position) = lemma_pos + + override def translateMember(t: Translator): Member = { + //Lemma(Method(method.idndef.name,method.formalArgs map t.liftVarDecl,method.formalReturns map t.liftVarDecl,method.pres map t.exp,method.posts map t.exp,Option(Seqn(method.body)))(lemma_pos)) + val newBody = method.body.map(actualBody => { + val b = t.stmt(actualBody).asInstanceOf[Seqn] + val newScopedDecls = b.scopedSeqnDeclarations ++ b.deepCollect { case l: Label => l } + + b.copy(scopedSeqnDeclarations = newScopedDecls)(b.pos, b.info, b.errT) + }) + + val finalMethod = Method(pres = method.pres map t.exp, posts = method.posts map t.exp, body = newBody, formalArgs = method.formalArgs map t.liftVarDecl, formalReturns = method.formalReturns map t.liftVarDecl, name = method.idndef.name)(method.pos._1) + + t.getMembers().addOne(method.idndef.name, finalMethod) + + finalMethod + } + override def translateMemberSignature(t: Translator): Member = { + Method(method.idndef.name, method.formalArgs map t.liftVarDecl, method.formalReturns map t.liftVarDecl, null, null, null)(method.pos._1) + } + */ +} + +case class POldCall(rets: Seq[PIdnUse], lbl:PIdnUse, method: PIdnUse, args:Seq[PExp])(val pos: (Position, Position)) extends PExtender with PStmt { + override val getSubnodes: Seq[PNode] = rets ++ Seq(method) ++ args + + override def typecheck(t: TypeChecker, n: NameAnalyser): Option[Seq[String]] = { + args.foreach(a => + t.checkTopTyped(a,None) + ) + None + } + + override def translateStmt(t: Translator): Stmt = { + //OldCall(rets.map(r => LocalVar(r.name, t.ttyp(r.typ))(t.liftPos(r))), Label(lbl.name, Seq() map t.exp)(t.liftPos(lbl)),t.exp(exp))(t.liftPos(this)) + OldCall(MethodCall(method.name, args map t.exp, rets.map(r => LocalVar(r.name,t.ttyp(r.typ))(t.liftPos(r))))(t.liftPos(method), NoInfo, NoTrafos), Label(lbl.name, Seq())(t.liftPos(lbl)))(t.liftPos(this)) + } } \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index 0dfc57bfc..113575030 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -7,7 +7,7 @@ import org.jgrapht.graph.{DefaultDirectedGraph, DefaultEdge} import viper.silver.ast._ import viper.silver.ast.utility.rewriter.Traverse import viper.silver.ast.utility.{Expressions, ViperStrategy} -import viper.silver.parser.FastParserCompanion.whitespace +import viper.silver.parser.FastParserCompanion.{LW, LeadingWhitespace, whitespace} import viper.silver.parser._ import viper.silver.plugin.standard.reasoning.analysis.{VarAnalysisGraph, VarAnalysisSet} import viper.silver.plugin.{ParserPluginTemplate, SilverPlugin} @@ -15,14 +15,14 @@ import viper.silver.verifier._ import scala.annotation.unused import scala.collection.mutable -import scala.jdk.CollectionConverters._ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, @unused logger: ch.qos.logback.classic.Logger, @unused config: viper.silver.frontend.SilFrontendConfig, - fp: FastParser) extends SilverPlugin with ParserPluginTemplate with VarAnalysisSet { + fp: FastParser) extends SilverPlugin with ParserPluginTemplate with VarAnalysisSet with BeforeVerifyHelper { - import fp.{FP, ParserExtension, block, exp, idndef, idnuse, keyword, trigger, typ} + import fp.{FP, ParserExtension, block, exp, idndef, idnuse, keyword, trigger, typ, formalArgList, pre, post, LeadingWhitespace, oldLabel, parens} + import viper.silver.parser.FastParserCompanion.LW override def reportErrorWithMsg(error: AbstractError): Unit = reportError(error) @@ -39,18 +39,16 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, /** Parser for new influence by condition */ def influenced_by[_: P]: P[PFlowAnnotation] = - //FP(keyword("influenced") ~/ (idndef ~ ":" ~ typ) ~/ keyword("by") ~ "{" ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ "}").map { case (pos, (v_idndef, v_typ, varList)) => PFlowAnalysis(v_idndef,v_typ, varList.map { case (id, typ) => (id, typ)})(pos)} - //FP(keyword("influenced") ~/ (idnuse) ~/ keyword("by") ~ "{" ~/ (idnuse).rep(sep = ",") ~/ "}").map { case (pos, (v_idnuse, varList)) => PFlowAnalysis(v_idnuse, varList)(pos) } P(keyword("influenced") ~/ (influenced_by_var | influenced_by_heap)) def influenced_by_var[_: P]: P[PFlowAnnotation] = { FP(idnuse ~/ keyword("by") ~ "{" ~/ (heap_then_vars | vars_then_opt_heap) ~/ "}").map { case (pos, (v_idnuse: PExp, (varList: Seq[PExp], None))) => - PFlowAnnotationVar(v_idnuse, varList)(pos) + PFlowAnnotation(PVar(v_idnuse)(v_idnuse.pos), varList.map(vl => PVar(vl)(vl.pos)))(pos) case (pos, (v_idnuse: PExp, varList: Option[Seq[PExp]])) => - PFlowAnnotationVarHeapArg(v_idnuse, varList.getOrElse(Seq()))(pos) + PFlowAnnotation(PVar(v_idnuse)(v_idnuse.pos), (varList.getOrElse(Seq()).map(vl => PVar(vl)(vl.pos))) ++ Seq(PHeap()(v_idnuse.pos)))(pos) case (pos, (v_idnuse: PExp, (varList1: Seq[PExp], varList2: Some[Seq[PExp]]))) => - PFlowAnnotationVarHeapArg(v_idnuse, varList1 ++ varList2.getOrElse(Seq()))(pos) + PFlowAnnotation(PVar(v_idnuse)(v_idnuse.pos), ((varList1 ++ varList2.getOrElse(Seq())).map(vl => PVar(vl)(vl.pos))) ++ Seq(PHeap()(v_idnuse.pos)))(pos) } } @@ -72,30 +70,50 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, P(keyword("heap") ~/ ("," ~/ idnuse.rep(sep = ",")).?) } + def influenced_by_heap[_: P]: P[PFlowAnnotation] = { FP(keyword("heap") ~/ keyword("by") ~ "{" ~/ (heap_then_vars | vars_then_opt_heap) ~/ "}").map { case (pos, (varList: Seq[PExp], None)) => - reportError(ParseError("The heap should always be influenced by the heap.", SourcePosition(pos._1.file,pos._1,pos._2))) - PFlowAnnotationHeap(varList)(pos) + reportError(ConsistencyError("The heap should always be influenced by the heap.", SourcePosition(pos._1.file, pos._1, pos._2))) + PFlowAnnotation(PHeap()(pos),varList.map(vl => PVar(vl)(vl.pos)))(pos) case (pos, varList: Option[Seq[PExp]]) => - PFlowAnnotationHeapHeapArg(varList.getOrElse(Seq()))(pos) + PFlowAnnotation(PHeap()(pos), (varList.getOrElse(Seq()).map(vl => PVar(vl)(vl.pos))) ++ Seq(PHeap()(pos)))(pos) case (pos, (varList1: Seq[PExp], varList2: Some[Seq[PExp]])) => - PFlowAnnotationHeapHeapArg(varList1 ++ varList2.getOrElse(Seq()))(pos) + PFlowAnnotation(PHeap()(pos), (varList1 ++ varList2.getOrElse(Seq())).map(vl => PVar(vl)(vl.pos)) ++ Seq(PHeap()(pos)))(pos) + } + } + + def lemma[_:P]: P[PLemma] = { + FP(keyword("isLemma")).map { case (pos,()) => PLemma()(pos)} + } + + def oldCall[_:P]: P[POldCall] = { + FP((idnuse.rep(sep = ",") ~ ":=").? ~ keyword("oldCall") ~ "[" ~ oldLabel ~ "]" ~ "(" ~ idnuse ~ parens(exp.rep(sep = ",")) ~ ")").map { + case (pos, (None, lbl, method, args)) => + POldCall(Nil, lbl, method, args)(pos) + case (pos, (Some(targets), lbl, method, args)) => + POldCall(targets, lbl, method, args)(pos) } } + /** Add existential elimination and universal introduction to the parser. */ override def beforeParse(input: String, isImported: Boolean): String = { // Add new keyword ParserExtension.addNewKeywords(Set[String]("obtain", "where", "prove", "forall", "assuming", "implies")) ParserExtension.addNewKeywords(Set[String]("influenced", "by", "heap")) + ParserExtension.addNewKeywords(Set[String]("isLemma")) + ParserExtension.addNewKeywords(Set[String]("oldCall")) ParserExtension.addNewStmtAtEnd(existential_elim(_)) ParserExtension.addNewStmtAtEnd(universal_intro(_)) - // Add new parser to the precondition - /** doesn't work because of return value in precondition? */ - //ParserExtension.addNewPreCondition(influenced_by(_)) // Add new parser to the postcondition ParserExtension.addNewPostCondition(influenced_by(_)) + + ParserExtension.addNewPreCondition(lemma(_)) + ParserExtension.addNewPostCondition(lemma(_)) + + ParserExtension.addNewStmtAtEnd(oldCall(_)) + input } @@ -103,91 +121,38 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, override def beforeVerify(input: Program): Program = { val usedNames: mutable.Set[String] = collection.mutable.Set(input.transitiveScopedDecls.map(_.name): _*) - def uniqueName(name: String): String = { - var i = 1 - var newName = name - while (usedNames.contains(newName)) { - newName = name + i - i += 1 - } - usedNames.add(newName) - newName - } + /** check that lemma terminates (has a decreases clause) and that it is pure */ + checkLemma(input, reportError) - def substituteWithFreshVars[E <: Exp](vars: Seq[LocalVarDecl], exp: E): (Seq[(LocalVarDecl, LocalVarDecl)], E) = { - val declMapping = vars.map(oldDecl => - oldDecl -> LocalVarDecl(uniqueName(oldDecl.name), oldDecl.typ)(oldDecl.pos, oldDecl.info, oldDecl.errT)) - val transformedExp = applySubstitution(declMapping, exp) - (declMapping, transformedExp) - } + /** check that influenced by expressions are exact or overapproximate the body of the method. */ + checkInfluencedBy(input, reportError) - def applySubstitution[E <: Exp](mapping: Seq[(LocalVarDecl, LocalVarDecl)], exp: E): E = { - Expressions.instantiateVariables(exp, mapping.map(_._1.localVar), mapping.map(_._2.localVar)) - } - /** check that influenced by expressions are exact or overapproximate the body of the method. */ - val body_graph_analysis: VarAnalysisGraph = VarAnalysisGraph(input, reportError) - input.methods.foreach(method => { - - val init_args_decl = method.formalArgs.map(a => body_graph_analysis.createInitialVertex(a)) - val init_rets_decl = method.formalReturns.map(r => body_graph_analysis.createInitialVertex(r)) - val method_vars: Map[LocalVarDecl, LocalVarDecl] = ((method.formalArgs zip init_args_decl) ++ (method.formalReturns zip init_rets_decl) ++ Seq((body_graph_analysis.heap_vertex, body_graph_analysis.createInitialVertex(body_graph_analysis.heap_vertex)))).toMap - val empty_body_graph = body_graph_analysis.createEmptyGraph(method_vars) - val body_graph = body_graph_analysis.compute_graph(empty_body_graph, method.body.getOrElse(Seqn(Seq(), Seq())()), method_vars) - //println("method body graph: " + body_graph_analysis.createDOT(body_graph)) - - val influenced_graph = body_graph_analysis.copyGraph(empty_body_graph) - method.posts.foreach { - case v@(FlowAnnotationVar(_, _) | FlowAnnotationVarHeapArg(_, _)) => - val (target, args) = v match { - case FlowAnnotationVar(t, a) => - (t, a) - case FlowAnnotationVarHeapArg(t, a) => - (t, a ++ Seq(body_graph_analysis.heap_vertex.localVar)) - } + ViperStrategy.Slim({ + case o@OldCall(mc,lbl) => + /** check whether called method is a lemma */ + val currmethod = input.findMethod(mc.methodName) + var isLemma:Boolean = currmethod.pres.exists(p => p.isInstanceOf[Lemma]) + isLemma = isLemma || currmethod.posts.exists(p => p.isInstanceOf[Lemma]) + + if (!isLemma) { + reportError(ConsistencyError(s"method ${currmethod.name} called in old context must be lemma", o.pos)) + } - val target_var: LocalVar = target.asInstanceOf[LocalVar] - val target_decl: LocalVarDecl = LocalVarDecl(target_var.name, target_var.typ)(target_var.pos) - args.foreach(arg => { - val arg_var: LocalVar = arg.asInstanceOf[LocalVar] - val arg_decl: LocalVarDecl = LocalVarDecl(arg_var.name, arg_var.typ)(arg_var.pos) - influenced_graph.addEdge(method_vars(arg_decl), target_decl, new DefaultEdge) - }) - case h@(FlowAnnotationHeap(_) | FlowAnnotationHeapHeapArg(_)) => - val args = h match { - case FlowAnnotationHeap(a) => - a - case FlowAnnotationHeapHeapArg(a) => - a ++ Seq(body_graph_analysis.heap_vertex.localVar) - } - args.foreach(arg => { - val arg_var: LocalVar = arg.asInstanceOf[LocalVar] - val arg_decl: LocalVarDecl = LocalVarDecl(arg_var.name, arg_var.typ)(arg_var.pos) - influenced_graph.addEdge(method_vars(arg_decl), body_graph_analysis.heap_vertex, new DefaultEdge) - }) - } - - - /** ignore the edges from the .init_ret to the ret vertex since before the method there is no init value of a return variable. */ - method.formalReturns.foreach(r => { - body_graph.removeAllEdges(method_vars(r), r) - }) - - /** the set of all incoming edges to the return variables of the method body graph should be a subset of the set of the incoming edges of the influenced by graph */ - method.formalReturns.foreach(r => { - body_graph.incomingEdgesOf(r).forEach(e => { - if (!influenced_graph.containsEdge(body_graph.getEdgeSource(e), body_graph.getEdgeTarget(e))) { - val ret_sources: String = body_graph.incomingEdgesOf(r).asScala.map(e => body_graph.getEdgeSource(e).name).toSet.mkString(", ").replace(".init_", "").replace(".", "") - reportError(ConsistencyError("influenced by expression may be incorrect. Possible influenced by expression: \n" + "influenced " + r.name.replace(".", "") + " by {" + ret_sources + "}", r.pos)) - } - }) - }) - }) + + Seqn( + currmethod.pres.map(p => + Assert(LabelledOld(p, lbl.name)(p.pos))(o.pos) + ) ++ + currmethod.posts.map(p => + Assume(LabelledOld(p, lbl.name)(p.pos))(o.pos) + ), + Seq() + )(o.pos) - ViperStrategy.Slim({ - case e@ExistentialElim(v, trigs, exp) => // e = ExistentialElim(vardecl, exp) - val (new_v_map, new_exp) = substituteWithFreshVars(v, exp) + case e@ExistentialElim(v, trigs, exp) => + val (new_v_map, new_exp) = substituteWithFreshVars(v, exp, usedNames) val new_trigs = trigs.map(t => Trigger(t.exps.map(e1 => applySubstitution(new_v_map, e1)))(t.pos)) Seqn( Seq( @@ -202,52 +167,36 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, Seq() )(e.pos) - /** remove the influenced by postconditions. */ + /** remove the influenced by postconditions. + * remove isLemma*/ case m: Method => var postconds: Seq[Exp] = Seq() m.posts.foreach { case _: FlowAnnotation => postconds = postconds + case _: Lemma => + postconds = postconds case s@_ => postconds = postconds ++ Seq(s) } + var preconds: Seq[Exp] = Seq() + m.pres.foreach { + case _: Lemma => + preconds = preconds + case s@_ => + preconds = preconds ++ Seq(s) + } + val newMethod = - if (postconds != m.posts) { - m.copy(pres = m.pres, posts = postconds)(m.pos, m.info, m.errT) + if (postconds != m.posts || preconds != m.pres) { + m.copy(pres = preconds, posts = postconds)(m.pos, m.info, m.errT) } else { m } newMethod case u@UniversalIntro(v, trigs, exp1, exp2, blk) => - val boolvar = LocalVarDecl(uniqueName("b"), Bool)(exp1.pos) - - /** check whether immutable universal introduction variables are reassigned - * if non empty set returned, immutable variables contained in there might have been reassigned. */ - /* - val reassigned = check_is_reassigned(v,blk) - if (reassigned.nonEmpty) { - val reassigned_names: String = reassigned.mkString(", ") - val reassigned_pos: String = reassigned.map(_.pos).mkString(", ") - reportError(ConsistencyError("Universal Introduction variable(s) (" + reassigned_names + ") might have been reassigned at position(s) (" + reassigned_pos + ")", u.pos)) - } - */ - - - /** - * get all variables that are assigned to inside the block and take intersection with universal introduction - * variables. If they are contained throw error since these variables should be immutable - */ - val written_vars: Option[Set[LocalVarDecl]] = writtenTo(blk) - if (written_vars.isDefined) { - val reassigned_vars: Set[LocalVarDecl] = written_vars.get.intersect(v.toSet) - if (reassigned_vars.nonEmpty) { - val reassigned_names: String = reassigned_vars.mkString(", ") - val reassigned_pos: String = reassigned_vars.map(_.pos).mkString(", ") - reportError(ConsistencyError("Universal Introduction variable(s) (" + reassigned_names + ") might have been reassigned at position(s) (" + reassigned_pos + ")", u.pos)) - } - } - + val boolvar = LocalVarDecl(uniqueName("b", usedNames), Bool)(exp1.pos) val vars_outside_blk: mutable.Set[Declaration] = mutable.Set() @@ -257,29 +206,10 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, vars_outside_blk ++= mutable.Set(m.transitiveScopedDecls: _*) } })) + /** Variables declared in the universal introduction statement are tainted */ val tainted: Set[LocalVarDecl] = v.toSet - /* - /** - * SET VERSION - */ - /** check whether any additional variables are tainted inside of the block */ - var all_tainted = Set[Declaration]() - all_tainted = get_tainted_vars_stmt(tainted, blk) - - - /** remove the variables that were tainted to begin with */ - vars_outside_blk --= mutable.Set(u.transitiveScopedDecls: _*) - - /** check whether any variables were tainted that are declared outside of our block */ - if (!(vars_outside_blk.intersect(all_tainted).isEmpty)) { - val tainted_vars: Set[Declaration] = all_tainted.intersect(vars_outside_blk) - val problem_vars: String = tainted_vars.mkString(", ") - val problem_pos: String = tainted_vars.map(vs => vs.pos).mkString(", ") - reportError(ConsistencyError("Universal introduction variable might have been assigned to variable " + problem_vars + " at positions (" + problem_pos + "), defined outside of the block", u.pos)) - } - */ /** * GRAPH VERSION @@ -287,83 +217,53 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, val graph_analysis: VarAnalysisGraph = VarAnalysisGraph(input, reportError) + /** create graph with vars that are in scope */ vars_outside_blk --= mutable.Set(u.transitiveScopedDecls: _*) vars_outside_blk ++= v - var graph: Graph[LocalVarDecl, DefaultEdge] = new DefaultDirectedGraph[LocalVarDecl, DefaultEdge](classOf[DefaultEdge]) - - /** old graph */ - //vars_outside_blk.foreach(v => graph.addVertex(v)) - - /** new graph */ + val graph: Graph[LocalVarDecl, DefaultEdge] = new DefaultDirectedGraph[LocalVarDecl, DefaultEdge](classOf[DefaultEdge]) var allVertices: Map[LocalVarDecl, LocalVarDecl] = Map[LocalVarDecl, LocalVarDecl]() - /* - var allVertices: Set[(LocalVarDecl, LocalVarDecl)] = Set() - */ + /** add heap variables to vertices */ allVertices += (graph_analysis.heap_vertex -> graph_analysis.createInitialVertex(graph_analysis.heap_vertex)) vars_outside_blk.foreach(v => { - val v_decl = v.asInstanceOf[LocalVarDecl] - val v_init = graph_analysis.createInitialVertex(v_decl) - allVertices += (v_decl -> v_init) - /* - allVertices = allVertices + ((v_init,v_decl)) - */ - graph.addVertex(v_init) - graph.addVertex(v_decl) - }) - //println(allVertices) - //println("first graph:") - //println(createDOT(graph)) - /** old graph */ - //graph = compute_graph(graph, blk) - - /** new graph */ - //println("ReasoningPlugin initial graph:", graph) - /* - graph = compute_graph(graph, blk, allVertices) - */ - - graph = graph_analysis.compute_graph(graph, blk, allVertices) - - //for debugging - //println("ReasoningPlugin final Graph") - //println(graph_analysis.createDOT(graph)) - - var noEdges: Boolean = true - var badEdges = Set[DefaultEdge]() - tainted.foreach(v => { - if (graph.edgesOf(graph_analysis.createInitialVertex(v)).size() > 1) { - badEdges = badEdges ++ graph.edgesOf(graph_analysis.createInitialVertex(v)).asScala.toSet[DefaultEdge] - noEdges = false + if (v.isInstanceOf[LocalVarDecl]) { + val v_decl = v.asInstanceOf[LocalVarDecl] + val v_init = graph_analysis.createInitialVertex(v_decl) + allVertices += (v_decl -> v_init) + + graph.addVertex(v_init) + graph.addVertex(v_decl) } - //noEdges = noEdges && graph.edgesOf(createInitialVertex(v)).size() <= 1 }) - if (!noEdges) { - //println("entered not empty thingy") - var tainted_vars: Set[LocalVarDecl] = Set() - //graph.edgeSet().forEach(e => { - badEdges.foreach(e => { - //println("edge inside:", e) - val target = graph.getEdgeTarget(e) - if (!tainted.contains(target)) { - tainted_vars = tainted_vars + graph.getEdgeTarget(e) - } - }) - val problem_vars: String = tainted_vars.mkString(", ") - val problem_pos: String = tainted_vars.map(vs => vs.pos).mkString(", ") - reportError(ConsistencyError("Universal introduction variable might have been assigned to variable " + problem_vars + " at positions (" + problem_pos + "), defined outside of the block", u.pos)) - } - val (new_v_map, new_exp1) = substituteWithFreshVars(v, exp1) + /** + * get all variables that are assigned to inside the block and take intersection with universal introduction + * variables. If they are contained throw error since these variables should be immutable + */ + val written_vars: Option[Set[LocalVarDecl]] = graph_analysis.writtenTo(allVertices ,blk) + checkReassigned(written_vars, v, reportError, u) + + graph_analysis.executeTaintedGraphAnalysis(graph, tainted, blk, allVertices, u) + + + /** + * SET VERSION + */ + /* + val tainted_decls: Set[Declaration] = tainted.map(t => t.asInstanceOf[Declaration]) + executeTaintedSetAnalysis(tainted_decls, vars_outside_blk, blk, u, reportError) + */ + + val (new_v_map, new_exp1) = substituteWithFreshVars(v, exp1, usedNames) val new_exp2 = applySubstitution(new_v_map, exp2) val arb_vars = new_v_map.map(vars => vars._2) val new_trigs = trigs.map(t => Trigger(t.exps.map(e1 => applySubstitution(new_v_map, e1)))(t.pos)) // label should also be in used Names => can also use uniqueName function - val lbl = uniqueName("l") + val lbl = uniqueName("l", usedNames) Seqn( @@ -387,30 +287,4 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, }, Traverse.TopDown).execute(input) } - - - /* - override def mapVerificationResult(input: VerificationResult): VerificationResult = { - val errors: Seq[AbstractError] = input match { - case Success => Seq() - case Failure(errors) => - errors.map { - case af@AssertFailed(a, err_reason, c) => - if (a.info == ExistentialElim) { - ExistentialElimFailed(a, err_reason, c) - } - else if (a.info == UniversalIntro) { - UniversalIntroFailed(a, err_reason, c) - } else if ((a.info == FlowAnnotationVar) || (a.info == FlowAnnotationHeap) || a.info == FlowAnnotationVarHeapArg || a.info == FlowAnnotationHeapHeapArg) { - FlowAnalysisFailed(a, err_reason, c) - } else { - af - } - - } - } - if (errors.length == 0) Success - else Failure(errors) - } - */ } \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala index daa625958..d847eea6c 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala @@ -2,9 +2,12 @@ package viper.silver.plugin.standard.reasoning.analysis import org.jgrapht.Graph import org.jgrapht.graph.{AbstractBaseGraph, DefaultDirectedGraph, DefaultEdge} -import viper.silver.ast.{AccessPredicate, Assert, Assume, BinExp, CurrentPerm, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, ForPerm, FuncApp, If, Inhale, Int, Label, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Perm, Program, Ref, Seqn, Stmt, UnExp, While} -import viper.silver.plugin.standard.reasoning.{ExistentialElim, FlowAnnotationHeap, FlowAnnotationHeapHeapArg, FlowAnnotationVar, FlowAnnotationVarHeapArg, UniversalIntro} -import viper.silver.verifier.AbstractError +import viper.silver.ast.{AccessPredicate, Apply, Assert, Assume, BinExp, CurrentPerm, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, Fold, ForPerm, FuncApp, Goto, If, Inhale, Int, Label, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Package, Perm, Program, Ref, Seqn, Stmt, UnExp, Unfold, While} +import viper.silver.plugin.standard.reasoning.{FlowAnnotation, OldCall, Var} +//import viper.silver.plugin.standard.reasoning.{ExistentialElim, FlowAnnotationHeap, FlowAnnotationHeapHeapArg, FlowAnnotationVar, FlowAnnotationVarHeapArg, UniversalIntro} +import viper.silver.plugin.standard.reasoning.{ExistentialElim, UniversalIntro} + +import viper.silver.verifier.{AbstractError, ConsistencyError} import java.io.StringWriter import scala.jdk.CollectionConverters._ @@ -20,6 +23,35 @@ case class VarAnalysisGraph(prog: Program, val heap_vertex: LocalVarDecl = LocalVarDecl(".heap", Ref)() + def executeTaintedGraphAnalysis(graph1: Graph[LocalVarDecl,DefaultEdge], tainted: Set[LocalVarDecl], blk: Seqn, allVertices: Map[LocalVarDecl, LocalVarDecl], u: UniversalIntro): Unit = { + + + val graph = compute_graph(graph1, blk, allVertices) + + + var noEdges: Boolean = true + var badEdges = Set[DefaultEdge]() + tainted.foreach(v => { + if (graph.edgesOf(createInitialVertex(v)).size() > 1) { + badEdges = badEdges ++ graph.edgesOf(createInitialVertex(v)).asScala.toSet[DefaultEdge] + noEdges = false + } + }) + if (!noEdges) { + var tainted_vars: Set[LocalVarDecl] = Set() + badEdges.foreach(e => { + val target = graph.getEdgeTarget(e) + if (!tainted.contains(target)) { + tainted_vars = tainted_vars + graph.getEdgeTarget(e) + } + }) + val problem_vars: String = tainted_vars.mkString(", ") + val problem_pos: String = tainted_vars.map(vs => vs.pos).mkString(", ") + reportErrorWithMsg(ConsistencyError("Universal introduction variable might have been assigned to variable " + problem_vars + " at positions (" + problem_pos + "), defined outside of the block", u.pos)) + } + } + + /** * Creates the Vertex that represents the initial value of the variable before the statement is executed * @param variable Variable for which we want to create the Vertex which represents the initial value of the variable @@ -231,9 +263,11 @@ case class VarAnalysisGraph(prog: Program, case Seqn(ss, scopedSeqnDeclarations) => var allVertices: Map[LocalVarDecl,LocalVarDecl] = vertices for (d <- scopedSeqnDeclarations) { - val d_decl = d.asInstanceOf[LocalVarDecl] - val d_init = createInitialVertex(d_decl) - allVertices += (d_decl -> d_init) + if (d.isInstanceOf[LocalVarDecl]) { + val d_decl = d.asInstanceOf[LocalVarDecl] + val d_init = createInitialVertex(d_decl) + allVertices += (d_decl -> d_init) + } } var new_graph: Graph[LocalVarDecl, DefaultEdge] = createIdentityGraph(allVertices) for (s <- ss) { @@ -309,7 +343,7 @@ case class VarAnalysisGraph(prog: Program, for (v <- rhs_vars) { /** if the variable on the right hand side is a field access */ - if (v.typ == Ref || v.typ == Perm) { + if (v.equals(heap_vertex)) { val heap_init = vertices(heap_vertex) new_graph.addEdge(heap_init, lhs_decl, new DefaultEdge) } else { @@ -323,20 +357,27 @@ case class VarAnalysisGraph(prog: Program, new_graph = addMissingEdges(new_graph, vert_wout_lhs) new_graph - case Inhale(exp) => + case Inhale(exp) => expInfluencesAll(exp, graph, vertices) + /* val id_graph = createIdentityGraph(vertices) - if (exp.isPure) { - graph - } else { - val inhale_vars = getVarsFromExpr(graph, exp) - inhale_vars.foreach(v => { - if (v.typ == Ref) { - val init_v = createInitialVertex(v) - id_graph.addEdge(init_v, heap_vertex, new DefaultEdge) - } + val inhale_vars = getVarsFromExpr(graph, exp) + inhale_vars.foreach(v => { + //val init_v = createInitialVertex(v) + val init_v = vertices(v) + + //id_graph.addEdge(init_v, heap_vertex, new DefaultEdge) + vertices.keySet.foreach(k => { + id_graph.addEdge(init_v, k, new DefaultEdge) + println(s"edge from ${init_v} to ${k}") }) - id_graph - } + + }) + id_graph + */ + + + /** same as inhale */ + case Assume(exp) => expInfluencesAll(exp, graph, vertices) case Exhale(exp) => val id_graph = createIdentityGraph(vertices) @@ -353,8 +394,7 @@ case class VarAnalysisGraph(prog: Program, id_graph } - case Assume(_) => - graph + case Label(_, _) => graph @@ -372,7 +412,7 @@ case class VarAnalysisGraph(prog: Program, val rhs_vars = getVarsFromExpr(graph, rhs) rhs_vars.foreach(v => { /** Edge from .init_heap to heap does not have to be added since it exists anyways */ - if (v.typ == Ref || v.typ == Perm || v == heap_vertex) { + if (v.equals(heap_vertex)) { id_graph } else { val v_init = createInitialVertex(v) @@ -384,16 +424,76 @@ case class VarAnalysisGraph(prog: Program, case ExistentialElim(_,_,_) => graph case UniversalIntro(varList,_,_,_,blk) => - val new_vertices: Map[LocalVarDecl, LocalVarDecl] = vertices ++ varList.map(v => (v,createInitialVertex(v))) - compute_graph(graph,blk,new_vertices) + val new_vertices: Map[LocalVarDecl, LocalVarDecl] = vertices ++ varList.map(v => (v -> createInitialVertex(v))) + val new_graph = compute_graph(graph,blk,new_vertices) + varList.foreach(v => { + new_graph.removeVertex(v) + new_graph.removeVertex(new_vertices(v)) + }) + new_graph case Assert(_) => graph + case Fold(acc) => + val id_graph = createIdentityGraph(vertices) + val vars = getVarsFromExpr(graph, acc) + vars.foreach(v => { + id_graph.addEdge(vertices(v), heap_vertex, new DefaultEdge) + }) + id_graph + + case Unfold(acc) => + val id_graph = createIdentityGraph(vertices) + val vars = getVarsFromExpr(graph, acc) + vars.foreach(v => { + id_graph.addEdge(vertices(v), heap_vertex, new DefaultEdge) + }) + id_graph + + case Apply(exp) => + val id_graph = createIdentityGraph(vertices) + val vars = getVarsFromExpr(graph, exp) + vars.foreach(v => { + id_graph.addEdge(vertices(v), heap_vertex, new DefaultEdge) + }) + id_graph + + case Package(wand, _) => + val id_graph = createIdentityGraph(vertices) + val vars = getVarsFromExpr(graph, wand) + vars.foreach(v => { + id_graph.addEdge(vertices(v), heap_vertex, new DefaultEdge) + }) + id_graph + + case g@Goto(_) => + reportErrorWithMsg(ConsistencyError(s"${g} is an undefined statement for the universal introduction block", g.pos)) + graph + + case OldCall(_,_) => graph case s@_ => - throw new UnsupportedOperationException("undefined statement for universal introduction block: " + s) + reportErrorWithMsg(ConsistencyError(s"${s} is an undefined statement for the universal introduction block", s.pos)) + graph + //throw new UnsupportedOperationException("undefined statement for universal introduction block: " + s) } } + def expInfluencesAll(exp:Exp, graph: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl,LocalVarDecl]) : Graph[LocalVarDecl, DefaultEdge] = { + val id_graph = createIdentityGraph(vertices) + val inhale_vars = getVarsFromExpr(graph, exp) + inhale_vars.foreach(v => { + //val init_v = createInitialVertex(v) + val init_v = vertices(v) + + //id_graph.addEdge(init_v, heap_vertex, new DefaultEdge) + vertices.keySet.foreach(k => { + id_graph.addEdge(init_v, k, new DefaultEdge) + }) + + }) + id_graph + } + def createInfluencedByGraph(graph: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl,LocalVarDecl], arg_names: Seq[Exp], ret_names: Seq[LocalVar], method_arg_names: Seq[LocalVarDecl], method_ret_names: Seq[LocalVarDecl], posts: Seq[Exp]): Graph[LocalVarDecl, DefaultEdge] = { /** set of all target variables that have not been included in the influenced by expression up until now */ var retSet: Set[LocalVarDecl] = method_ret_names.toSet + heap_vertex @@ -448,17 +548,10 @@ case class VarAnalysisGraph(prog: Program, /** need to add the edges from the influenced by expression */ posts.foreach { - case v@(FlowAnnotationVar(_,_) | FlowAnnotationVarHeapArg(_,_)) => - /** depending on whether or not heap is method argument get argument in influenced by statement */ - val (returned, arguments) = v match { - case FlowAnnotationVar(r,a) => - (r,a) - case FlowAnnotationVarHeapArg(r,a) => - (r,a++Seq(heap_vertex.localVar)) - } + case FlowAnnotation(returned, arguments) => /** returned has to be instance of LocalVar */ - val returned_var: LocalVar = returned.asInstanceOf[LocalVar] + val returned_var: LocalVar = if (returned.isInstanceOf[Var]) returned.asInstanceOf[Var].var_decl.asInstanceOf[LocalVar] else heap_vertex.localVar /** create LocalVarDecl such that it can be added in the graph */ val return_decl = LocalVarDecl(returned_var.name, returned_var.typ)(returned_var.pos) retSet -= return_decl @@ -469,32 +562,12 @@ case class VarAnalysisGraph(prog: Program, arguments.foreach(argument => { /** argument has to be instance of LocalVar */ - val argument_var: LocalVar = argument.asInstanceOf[LocalVar] + val argument_var: LocalVar = if (argument.isInstanceOf[Var]) argument.asInstanceOf[Var].var_decl.asInstanceOf[LocalVar] else heap_vertex.localVar val argument_decl = LocalVarDecl(argument_var.name, argument_var.typ)(argument_var.pos) /** get corresponding .arg variable and add edge from .arg to .ret vertex */ val prov_decl = method_args(argument_decl) methodcall_graph.addEdge(prov_decl, method_rets(return_decl), new DefaultEdge) }) - case h@(FlowAnnotationHeap(_) | FlowAnnotationHeapHeapArg(_) )=> - if (!methodcall_graph.containsVertex(method_rets(heap_vertex))) { - methodcall_graph.addVertex(method_rets(heap_vertex)) - } - retSet -= heap_vertex - - val arguments = h match { - case FlowAnnotationHeap(a) => - a - case FlowAnnotationHeapHeapArg(a) => - a ++ Seq(heap_vertex.localVar) - } - - arguments.foreach(argument => { - /** argument has to be instance of LocalVar */ - val argument_var: LocalVar = argument.asInstanceOf[LocalVar] - val argument_decl = LocalVarDecl(argument_var.name, argument_var.typ)(argument_var.pos) - val prov_decl = method_args(argument_decl) - methodcall_graph.addEdge(prov_decl, method_rets(heap_vertex), new DefaultEdge) - }) } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala index d15cd164e..6cba38796 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala @@ -1,6 +1,7 @@ package viper.silver.plugin.standard.reasoning.analysis import viper.silver.ast.{Assume, BinExp, Declaration, Exp, FieldAssign, If, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Seqn, Stmt, UnExp, While} +import viper.silver.plugin.standard.reasoning.UniversalIntro import viper.silver.verifier.{AbstractError, ConsistencyError} import scala.collection.mutable @@ -9,6 +10,25 @@ trait VarAnalysisSet { def reportErrorWithMsg(error: AbstractError): Unit + + def executeTaintedSetAnalysis(tainted: Set[Declaration], vars_outside_blk: mutable.Set[Declaration], blk: Seqn, u: UniversalIntro, reportError: AbstractError => Unit): Unit = { + /** check whether any additional variables are tainted inside of the block */ + var all_tainted = Set[Declaration]() + all_tainted = get_tainted_vars_stmt(tainted, blk) + + + /** remove the variables that were tainted to begin with */ + vars_outside_blk --= mutable.Set(u.transitiveScopedDecls: _*) + + /** check whether any variables were tainted that are declared outside of our block */ + if (!(vars_outside_blk.intersect(all_tainted).isEmpty)) { + val tainted_vars: Set[Declaration] = all_tainted.intersect(vars_outside_blk) + val problem_vars: String = tainted_vars.mkString(", ") + val problem_pos: String = tainted_vars.map(vs => vs.pos).mkString(", ") + reportError(ConsistencyError("Universal introduction variable might have been assigned to variable " + problem_vars + " at positions (" + problem_pos + "), defined outside of the block", u.pos)) + } + } + /** * check which arguments are influenced by universal introduction variables and add them to the tainted set. * @param tainted diff --git a/src/test/resources/reasoning/test.vpr b/src/test/resources/reasoning/test.vpr index 7e338eece..adf6a5ef0 100644 --- a/src/test/resources/reasoning/test.vpr +++ b/src/test/resources/reasoning/test.vpr @@ -7,7 +7,6 @@ function Q(x: Int) : Bool { } field f: Int - method simple() { var z: Int := 0 @@ -136,7 +135,6 @@ method mInhaleOK2(y: Ref) } - method mExhaleOK(y:Ref) requires acc(y.f) { @@ -277,8 +275,8 @@ method mMethodCallOK3() } -//:: ExpectedOutput(consistency.error) method exampleincorrect(b:Bool,c:Int) returns (z:Int, w:Int) +//:: ExpectedOutput(consistency.error) influenced w by {} { z := 3 @@ -304,7 +302,7 @@ influenced w by {b,c} method exampleHeapArg2(b:Ref) returns (c:Int) requires acc(b.f) influenced heap by { heap } -influenced c by { heap } +influenced c by { heap , b } { c := b.f } @@ -325,6 +323,83 @@ influenced c by { heap, b } c := b.f } +method exampleWrongInfluenced() returns (c:Int) +//:: ExpectedOutput(consistency.error) +influenced c by {c} +{ + c := 0 +} +//:: ExpectedOutput(consistency.error) +method l1() +isLemma +{ + var z:Int := 3 +} +method l2() +decreases +isLemma +{ + var x:Int + //:: ExpectedOutput(consistency.error) + inhale(x == 0) +} + +//:: ExpectedOutput(consistency.error) +method l3() +isLemma +decreases * +{ + var x:Int := 0 + while (x>0) { + x := x+1 + } +} + +method l4() +isLemma +decreases +{ + var t: Int := 0 +} +predicate foo(xs:Ref) +{ + acc(xs.f) +} + +method mFold() +{ + var z: Ref + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + assume acc(z.f) + fold foo(z) + } +} + +method mFoldNOK() +{ + var z: Ref + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + z.f := x + assume acc(z.f) + fold foo(z) + } +} + +method mOldCallOK() +{ + label l + oldCall[l](l3()) +} + +method mOldCallNOK() +{ + var x: Int := 0 + label l + x := 2 + //:: ExpectedOutput(consistency.error) + oldCall[l](exampleHeap(x)) +} From 2118cef78d9089ee5edb3cf31d79eebfa5577959 Mon Sep 17 00:00:00 2001 From: Dina Weiersmueller Date: Tue, 21 Mar 2023 10:42:19 +0100 Subject: [PATCH 07/41] Complete Bachelor Thesis --- .../reasoning/BeforeVerifyHelper.scala | 90 ++- .../reasoning/ReasoningASTExtension.scala | 127 +--- .../reasoning/ReasoningPASTExtension.scala | 108 +--- .../standard/reasoning/ReasoningPlugin.scala | 265 ++++++--- .../analysis/SetGraphComparison.scala | 146 +++++ .../reasoning/analysis/VarAnalysisGraph.scala | 554 ++++-------------- .../reasoning/analysis/VarAnalysisSet.scala | 238 +------- .../resources/reasoning/existential_elim.vpr | 31 + .../reasoning/existential_elim_impure.vpr | 9 - .../reasoning/existential_elim_simple.vpr | 10 - .../reasoning/existential_elim_trigger.vpr | 11 - src/test/resources/reasoning/immutableVar.vpr | 17 +- .../resources/reasoning/influenced_heap.vpr | 20 + src/test/resources/reasoning/set_vs_graph.vpr | 170 ++++++ src/test/resources/reasoning/test.vpr | 117 +++- src/test/resources/reasoning/test2.vpr | 127 ++++ ...BlockAssignmentUnivIntro.vpr => test3.vpr} | 0 ...l_intro_simple.vpr => universal_intro.vpr} | 20 + .../universal_intro_assuming_false.vpr | 19 - 19 files changed, 1001 insertions(+), 1078 deletions(-) create mode 100644 src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala create mode 100644 src/test/resources/reasoning/existential_elim.vpr delete mode 100644 src/test/resources/reasoning/existential_elim_impure.vpr delete mode 100644 src/test/resources/reasoning/existential_elim_simple.vpr delete mode 100644 src/test/resources/reasoning/existential_elim_trigger.vpr create mode 100644 src/test/resources/reasoning/influenced_heap.vpr create mode 100644 src/test/resources/reasoning/set_vs_graph.vpr create mode 100644 src/test/resources/reasoning/test2.vpr rename src/test/resources/reasoning/{outsideBlockAssignmentUnivIntro.vpr => test3.vpr} (100%) rename src/test/resources/reasoning/{universal_intro_simple.vpr => universal_intro.vpr} (70%) delete mode 100644 src/test/resources/reasoning/universal_intro_assuming_false.vpr diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala index f846de1fd..32cadaa8b 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala @@ -4,7 +4,7 @@ import org.jgrapht.graph.DefaultEdge import viper.silver.ast.utility.Expressions import viper.silver.ast.{Apply, Exhale, Exp, FieldAssign, Fold, Inhale, LocalVar, LocalVarDecl, Method, MethodCall, Package, Position, Program, Seqn, Stmt, Unfold} import viper.silver.plugin.standard.reasoning.analysis.VarAnalysisGraph -import viper.silver.plugin.standard.termination.{DecreasesSpecification, DecreasesTuple, DecreasesWildcard} +import viper.silver.plugin.standard.termination.{DecreasesSpecification, DecreasesStar, DecreasesTuple, DecreasesWildcard} import viper.silver.verifier.{AbstractError, ConsistencyError} import scala.collection.mutable @@ -14,9 +14,8 @@ trait BeforeVerifyHelper { - /** methods to rename variables */ + /** methods to rename variables for the encoding of the new syntax */ def uniqueName(name: String, usedNames: mutable.Set[String]): String = { - println("usedNames: " + usedNames) var i = 1 var newName = name while (usedNames.contains(newName)) { @@ -38,13 +37,23 @@ trait BeforeVerifyHelper { Expressions.instantiateVariables(exp, mapping.map(_._1.localVar), mapping.map(_._2.localVar)) } + def applySubstitutionWithExp[E <: Exp](mapping: Seq[(LocalVarDecl, Exp)], exp: E): E = { + Expressions.instantiateVariables(exp, mapping.map(_._1.localVar), mapping.map(_._2)) + } + + /** * get all variables that are assigned to inside the block and take intersection with universal introduction * variables. If they are contained throw error since these variables should be immutable + * + * @param modified_vars: set of variables that were modified in a given statement + * @param quantified_vars: set of quantified variables in the universal introduction statement. + * @param reportError: Method to report the error when a qunatified variable was modified + * @param u: universal introduction statement, used for details in error message */ - def checkReassigned(written_vars: Option[Set[LocalVarDecl]], v: Seq[LocalVarDecl], reportError: AbstractError => Unit, u: UniversalIntro): Unit = { - if (written_vars.isDefined) { - val reassigned_vars: Set[LocalVarDecl] = written_vars.get.intersect(v.toSet) + def checkReassigned(modified_vars: Option[Set[LocalVarDecl]], quantified_vars: Seq[LocalVarDecl], reportError: AbstractError => Unit, u: UniversalIntro): Unit = { + if (modified_vars.isDefined) { + val reassigned_vars: Set[LocalVarDecl] = modified_vars.get.intersect(quantified_vars.toSet) if (reassigned_vars.nonEmpty) { val reassigned_names: String = reassigned_vars.mkString(", ") val reassigned_pos: String = reassigned_vars.map(_.pos).mkString(", ") @@ -67,7 +76,9 @@ trait BeforeVerifyHelper { containsDecreases = true case DecreasesWildcard(_) => containsDecreases = true - case s@_ => + case DecreasesStar() => + reportError(ConsistencyError("decreases statement might not prove termination", method.pos)) + case _ => () } /** check postconditions for decreases clause */ @@ -76,8 +87,10 @@ trait BeforeVerifyHelper { containsDecreases = true case DecreasesWildcard(_) => containsDecreases = true - case s@_ => - () + case DecreasesStar() => + reportError(ConsistencyError("decreases statement might not prove termination", method.pos)) + containsDecreases = true + case _ => () } /** check info for decreases specification */ @@ -85,13 +98,12 @@ trait BeforeVerifyHelper { case spec: DecreasesSpecification => if (spec.star.isDefined) { reportError(ConsistencyError("decreases statement might not prove termination", method.pos)) + containsDecreases = true } else { containsDecreases = true } case _ => } - - } /** report error if there is no decreases clause or specification */ @@ -108,7 +120,7 @@ trait BeforeVerifyHelper { } - /** checks whether the body is pure, reports Error if impure operation found */ + /** checks whether the body is pure, reports error if impure operation found */ def checkBodyPure(stmt: Stmt, method: Method, prog: Program, reportError: AbstractError => Unit): Boolean = { var pure: Boolean = true stmt match { @@ -117,6 +129,8 @@ trait BeforeVerifyHelper { pure = pure && checkBodyPure(s, method, prog, reportError) }) pure + + /** case for statements considered impure */ case ie@(Inhale(_) | Exhale(_) | FieldAssign(_, _) | Fold(_) | Unfold(_) | Apply(_) | Package(_, _)) => reportError(ConsistencyError(s"method ${method.name} marked lemma might contain impure statement ${ie}", ie.pos)) false @@ -147,36 +161,53 @@ trait BeforeVerifyHelper { /** helper variable for error message, maps local variable to its position */ var influenced_by_pos: Map[LocalVarDecl, Position] = Map() + val init_args_decl = method.formalArgs.map(a => body_graph_analysis.createInitialVertex(a)) val init_rets_decl = method.formalReturns.map(r => body_graph_analysis.createInitialVertex(r)) val method_vars: Map[LocalVarDecl, LocalVarDecl] = ((method.formalArgs zip init_args_decl) ++ (method.formalReturns zip init_rets_decl) ++ Seq((body_graph_analysis.heap_vertex, body_graph_analysis.createInitialVertex(body_graph_analysis.heap_vertex)))).toMap val empty_body_graph = body_graph_analysis.createEmptyGraph(method_vars) - val body_graph = body_graph_analysis.compute_graph(empty_body_graph, method.body.getOrElse(Seqn(Seq(), Seq())()), method_vars) val heap_vert = LocalVarDecl(body_graph_analysis.heap_vertex.name, body_graph_analysis.heap_vertex.typ)() val influenced_graph = body_graph_analysis.copyGraph(empty_body_graph) + var influenced_exists: Boolean = false + /** iterate through method postconditions to find flow annotations */ method.posts.foreach { case v@FlowAnnotation(target, args) => + influenced_exists = true + /** create target variable of flowannotation based on whether it is the heap or another return variable */ val target_var: LocalVar = if (target.isInstanceOf[Var]) target.asInstanceOf[Var].var_decl.asInstanceOf[LocalVar] else body_graph_analysis.heap_vertex.localVar val target_decl: LocalVarDecl = LocalVarDecl(target_var.name, target_var.typ)(v.pos) + /** check whether the target variable is in fact a return variable */ if (!return_vars.contains(target_decl)) { reportError(ConsistencyError(s"Only return variables can be influenced and only one influenced by expression per return variable can exist. ${target_decl.name} may not be a return variable or might be used several times.", v.pos)) } + /** keep track of which return variables have an influenced by annotation */ return_vars -= target_decl influenced_by_pos += (target_decl -> v.pos) + + args.foreach(arg => { + + /** decide for each variable in the set of variables of the flow annotation whether they represent a normal variable or the heap */ val arg_var: LocalVar = if (arg.isInstanceOf[Var]) arg.asInstanceOf[Var].var_decl.asInstanceOf[LocalVar] else body_graph_analysis.heap_vertex.localVar val arg_decl: LocalVarDecl = LocalVarDecl(arg_var.name, arg_var.typ)(arg_var.pos) + + /** check that each variable in the set is a method argument */ if (!arg_vars.contains(arg_decl)) { reportError(ConsistencyError(s"Only argument variables can be influencing the return variable. ${arg_decl.name} may not be an argument variable.", v.pos)) } + + /** add corresponding edge from method argument to the target variable */ influenced_graph.addEdge(method_vars(arg_decl), target_decl, new DefaultEdge) }) + case _ => () } - + /** for all remaining variables that didn't have an influenced by annotation create an edge from every method argument to the return variable + * to overapproximate the information flow + */ return_vars.foreach(rv => { (method.formalArgs ++ Seq(body_graph_analysis.heap_vertex)).foreach(a => { if (!influenced_graph.containsVertex(method_vars(a))) { @@ -189,21 +220,28 @@ trait BeforeVerifyHelper { }) }) + /** for evaluation purposes if graph of method body should be created even though there is no influenced by annotation */ + //val body_graph = body_graph_analysis.compute_graph(method.body.getOrElse(Seqn(Seq(), Seq())()), method_vars) - /** ignore the edges from the .init_ret to the ret vertex since before the method there is no init value of a return variable. */ - method.formalReturns.foreach(r => { - body_graph.removeAllEdges(method_vars(r), r) - }) + /** if influenced by annotation exists create graph of the method body and check whether the influenced by expression is correct */ + if (influenced_exists) { + val body_graph = body_graph_analysis.compute_graph(method.body.getOrElse(Seqn(Seq(), Seq())()), method_vars) - /** the set of all incoming edges to the return variables of the method body graph should be a subset of the set of the incoming edges of the influenced by graph */ - (method.formalReturns ++ Seq(heap_vert)).foreach(r => { - body_graph.incomingEdgesOf(r).forEach(e => { - if (!influenced_graph.containsEdge(body_graph.getEdgeSource(e), body_graph.getEdgeTarget(e))) { - val ret_sources: String = body_graph.incomingEdgesOf(r).asScala.map(e => body_graph.getEdgeSource(e).name).toSet.mkString(", ").replace(".init_", "").replace(".", "") - reportError(ConsistencyError("influenced by expression may be incorrect. Possible influenced by expression: \n" + "influenced " + r.name.replace(".", "") + " by {" + ret_sources + "}", influenced_by_pos(r))) - } + /** ignore the edges from the .init_ret to the ret vertex since before the method there is no init value of a return variable. */ + method.formalReturns.foreach(r => { + body_graph.removeAllEdges(method_vars(r), r) }) - }) + + /** the set of all incoming edges to the return variables of the method body graph should be a subset of the set of the incoming edges of the influenced by graph */ + (method.formalReturns ++ Seq(heap_vert)).foreach(r => { + body_graph.incomingEdgesOf(r).forEach(e => { + if (!influenced_graph.containsEdge(body_graph.getEdgeSource(e), body_graph.getEdgeTarget(e))) { + val ret_sources: String = body_graph.incomingEdgesOf(r).asScala.map(e => body_graph.getEdgeSource(e).name).toList.sortWith(_ < _).mkString(", ").replace(".init_", "").replace(".", "") + reportError(ConsistencyError("influenced by expression may be incorrect. Possible influenced by expression: \n" + "influenced " + r.name.replace(".", "") + " by {" + ret_sources + "}", influenced_by_pos(r))) + } + }) + }) + } }) } } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala index 5abdb0486..189213f58 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala @@ -1,15 +1,12 @@ package viper.silver.plugin.standard.reasoning -import viper.silver.FastMessaging import viper.silver.ast._ -import viper.silver.ast.pretty.FastPrettyPrinter.{ContOps, braces, brackets, char, defaultIndent, group, line, lineIfSomeNonEmpty, nest, nil, parens, show, showBlock, showContracts, showStmt, showVars, space, ssep, text, toParenDoc} +import viper.silver.ast.pretty.FastPrettyPrinter.{ContOps, brackets, char, defaultIndent, group, line, nest, nil, parens, show, showBlock, showVars, space, ssep, text, toParenDoc} import viper.silver.ast.pretty.PrettyPrintPrimitives -import viper.silver.ast.utility.{Consistency, Expressions} -import viper.silver.plugin.standard.termination.PDecreasesClause +import viper.silver.ast.utility.{Consistency} import viper.silver.verifier.{ConsistencyError, Failure, VerificationResult} -/** An `FailureExpectedInfo` info that tells us that this assert is a existential elimination. */ case object ReasoningInfo extends FailureExpectedInfo case class ExistentialElim(varList: Seq[LocalVarDecl], trigs: Seq[Trigger], exp: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt { @@ -66,8 +63,7 @@ case class Var(decl:Exp)(val pos: Position = NoPosition, val info: Info = NoInfo def var_decl: Exp = decl } case class FlowAnnotation(v: FlowVar, varList: Seq[FlowVar])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionExp with Node with Scope { -//sealed trait FlowAnnotation extends ExtensionExp with Node with Scope { - override def extensionIsPure: Boolean = true + override def extensionIsPure: Boolean = true override val scopedDecls = Seq() @@ -98,58 +94,6 @@ case class FlowAnnotation(v: FlowVar, varList: Seq[FlowVar])(val pos: Position = } } -/* -case class FlowAnnotationVar(v: Exp, varList: Seq[Exp])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowAnnotation { - - override def extensionSubnodes: Seq[Node] = Seq(v) ++ varList - - /** Pretty printing functionality as defined for other nodes in class FastPrettyPrinter. - * Sample implementation would be text("old") <> parens(show(e)) for pretty-printing an old-expression. */ - override def prettyPrint: PrettyPrintPrimitives#Cont = { - text("influenced") <+> show(v) <+> - text("by") <+> - ssep(varList map show, group(char (',') <> line(" "))) - } -} - -case class FlowAnnotationVarHeapArg(v: Exp, varList: Seq[Exp])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowAnnotation { - - override def extensionSubnodes: Seq[Node] = Seq(v) ++ varList - override def prettyPrint: PrettyPrintPrimitives#Cont = { - text("influenced") <+> show(v) <+> - text("by") <+> - ssep(varList map show, group(char(',') <> line(" "))) <+> text(", heap") - } -} - -case class FlowAnnotationHeap(varList: Seq[Exp])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowAnnotation { - - override def extensionSubnodes: Seq[Node] = varList - - /** Pretty printing functionality as defined for other nodes in class FastPrettyPrinter. - * Sample implementation would be text("old") <> parens(show(e)) for pretty-printing an old-expression. */ - override def prettyPrint: PrettyPrintPrimitives#Cont = { - text("influenced heap") <+> - text("by") <+> - ssep(varList map show, group(char (',') <> line(" "))) - } -} - -case class FlowAnnotationHeapHeapArg(varList: Seq[Exp])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowAnnotation { - - override def extensionSubnodes: Seq[Node] = varList - - /** Pretty printing functionality as defined for other nodes in class FastPrettyPrinter. - * Sample implementation would be text("old") <> parens(show(e)) for pretty-printing an old-expression. */ - override def prettyPrint: PrettyPrintPrimitives#Cont = { - text("influenced heap") <+> - text("by") <+> - ssep(varList map show, group(char (',') <> line(" "))) <+> text(", heap") - - } -} - - */ case class Lemma()(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionExp with Node with Scope { @@ -172,66 +116,29 @@ case class Lemma()(val pos: Position = NoPosition, val info: Info = NoInfo, val text("isLemma") } +} - /* - override def extensionSubnodes: Seq[Node] = method.subnodes +case class OldCall(methodName: String, args: Seq[Exp], rets: Seq[LocalVar], l:Label)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt with Scope { + override val scopedDecls = Seq() override lazy val check: Seq[ConsistencyError] = { - - var errors: Seq[ConsistencyError] = Seq() - - var decreasesclauses = method.posts.filter(post => post.isInstanceOf[PDecreasesClause]) - decreasesclauses ++= method.pres.filter(pre => pre.isInstanceOf[PDecreasesClause]) - - if (decreasesclauses.isEmpty) { - errors ++= Seq(ConsistencyError("Lemma is not guaranteed to terminate. Add decreases clause.", this.pos)) - } - var pure = method.posts.filter(post => !post.isPure) - pure ++= method.pres.filter(pre => !pre.isPure) - if(pure.nonEmpty && method.pres.nonEmpty){ - errors ++= Seq(ConsistencyError("Lemma is not pure", this.pos)) - } - errors - } - override def prettyPrint: PrettyPrintPrimitives#Cont = { - group(text("lemma") <+> name <> nest(defaultIndent, parens(showVars(method.formalArgs))) <> { - if (method.formalReturns.isEmpty) nil - else nest(defaultIndent, line <> "returns" <+> parens(showVars(method.formalReturns))) - }) <> - nest(defaultIndent, - showContracts("requires", method.pres) <> - showContracts("ensures", method.posts) - ) <> - line <> ( - method.body match { - case None => - nil - case Some(actualBody) => - braces(nest(defaultIndent, - lineIfSomeNonEmpty(actualBody.children) <> - ssep(Seq(showStmt(actualBody)), line) - ) <> line) - }) + var s = Seq.empty[ConsistencyError] + if (!Consistency.noResult(this)) + s :+= ConsistencyError("Result variables are only allowed in postconditions of functions.", pos) + if (!Consistency.noDuplicates(rets)) + s :+= ConsistencyError("Targets are not allowed to have duplicates", rets.head.pos) + s ++= args.flatMap(Consistency.checkPure) + s } - override def name: String = method.name - - override val scopedDecls: Seq[Declaration] = method.scopedDecls - - */ -} - -case class OldCall(mc:MethodCall, l:Label)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt with Scope { - override val scopedDecls = Seq() - override lazy val prettyPrint: PrettyPrintPrimitives#Cont = { - val call = text("oldCall") <> brackets(show(l)) <> text(mc.methodName) <> nest(defaultIndent, parens(ssep(mc.args map show, group(char(',') <> line)))) - mc.targets match { + val call = text("oldCall") <> brackets(show(l)) <> text(methodName) <> nest(defaultIndent, parens(ssep(args map show, group(char(',') <> line)))) + rets match { case Nil => call - case _ => ssep(mc.targets map show, char(',') <> space) <+> ":=" <+> call + case _ => ssep(rets map show, char(',') <> space) <+> ":=" <+> call } } - override val extensionSubnodes: Seq[Node] = mc.args ++ mc.targets + override val extensionSubnodes: Seq[Node] = args ++ rets } \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala index 991fbd837..ba371f7be 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -1,10 +1,8 @@ package viper.silver.plugin.standard.reasoning -import viper.silver.FastMessaging -import viper.silver.ast.{ErrorTrafo, ExtensionExp, Info, Label, LocalVar, LocalVarDecl, Member, Method, MethodCall, NoInfo, NoPosition, NoTrafos, Position, Seqn, SourcePosition, Stmt, Trigger} +import viper.silver.ast.{ExtensionExp, Label, LocalVar, LocalVarDecl, Position, Seqn, Stmt, Trigger} import viper.silver.parser.TypeHelper.Bool -import viper.silver.parser.{NameAnalyser, PExp, PExtender, PGlobalDeclaration, PIdnDef, PIdnUse, PLabel, PLocalVarDecl, PMember, PMethod, PNode, PSeqn, PStmt, PTrigger, PType, PTypeSubstitution, Translator, TypeChecker} -import viper.silver.plugin.standard.termination.PDecreasesClause +import viper.silver.parser.{NameAnalyser, PExp, PExtender, PIdnUse, PLocalVarDecl, PNode, PSeqn, PStmt, PTrigger, PType, PTypeSubstitution, Translator, TypeChecker} case class PExistentialElim(varList: Seq[PLocalVarDecl], trig: Seq[PTrigger], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { override val getSubnodes: Seq[PNode] = { @@ -45,7 +43,6 @@ case class PUniversalIntro(varList: Seq[PLocalVarDecl], triggers: Seq[PTrigger], } case class PFlowAnnotation(v:PFlowVar, varList: Seq[PFlowVar])(val pos: (Position,Position)) extends PExtender with PExp { -//sealed trait PFlowAnnotation extends PExtender with PExp { override def typeSubstitutions: Seq[PTypeSubstitution] = Seq(PTypeSubstitution.id) override def forceSubstitution(ts: PTypeSubstitution): Unit = {} @@ -91,65 +88,6 @@ case class PVar(decl:PExp)(val pos: (Position,Position)) extends PFlowVar{ Var(t.exp(decl))(t.liftPos(this)) } } -/* -case class PFlowAnnotationVar(v:PExp, varList: Seq[PExp])(val pos: (Position,Position)) extends PFlowAnnotation { - - override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = { - varList.foreach(c => { - t.checkTopTyped(c, None) - }) - t.checkTopTyped(v, None) - None - } - - override def translateExp(t: Translator): ExtensionExp = { - FlowAnnotationVar(t.exp(v), varList.map { case variable => t.exp(variable) })(t.liftPos(this)) - - } -} - -/** specified type of the PFlowAnnotationVar class which includes heap as one of the argument variables */ -case class PFlowAnnotationVarHeapArg(v:PExp, varList: Seq[PExp])(val pos:(Position,Position)) extends PFlowAnnotation { - - override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = { - - varList.foreach(c => { - t.checkTopTyped(c, None) - }) - t.checkTopTyped(v, None) - None - } - override def translateExp(t: Translator): ExtensionExp = FlowAnnotationVarHeapArg(t.exp(v), varList.map { case variable => t.exp(variable) })(t.liftPos(this)) -} - -case class PFlowAnnotationHeap(varList: Seq[PExp])(val pos: (Position,Position)) extends PFlowAnnotation { - - override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = { - varList.foreach(c => t.checkTopTyped(c, None)) - None - } - - override def translateExp(t: Translator): ExtensionExp = { - FlowAnnotationHeap(varList.map { case variable => t.exp(variable) })(t.liftPos(this)) - - } -} - -case class PFlowAnnotationHeapHeapArg(varList: Seq[PExp])(val pos: (Position,Position)) extends PFlowAnnotation { - - override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = { - varList.foreach(c => t.checkTopTyped(c, None)) - None - } - - override def translateExp(t: Translator): ExtensionExp = { - FlowAnnotationHeapHeapArg(varList.map { case variable => t.exp(variable) })(t.liftPos(this)) - - } -} - - */ - case class PLemma()(val pos: (Position,Position)) extends PExtender with PExp { @@ -167,42 +105,6 @@ case class PLemma()(val pos: (Position,Position)) extends PExtender with PExp { Lemma()(t.liftPos(this)) } - /* - override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = { - None - } - - - override def getSubnodes(): Seq[PNode] = method.subnodes - - override def typecheck(t: TypeChecker, n: NameAnalyser): Option[Seq[String]] = { - t.checkMember(method)() - None - } - - override def idndef: PIdnDef = method.idndef - - override val pos: (Position, Position) = lemma_pos - - override def translateMember(t: Translator): Member = { - //Lemma(Method(method.idndef.name,method.formalArgs map t.liftVarDecl,method.formalReturns map t.liftVarDecl,method.pres map t.exp,method.posts map t.exp,Option(Seqn(method.body)))(lemma_pos)) - val newBody = method.body.map(actualBody => { - val b = t.stmt(actualBody).asInstanceOf[Seqn] - val newScopedDecls = b.scopedSeqnDeclarations ++ b.deepCollect { case l: Label => l } - - b.copy(scopedSeqnDeclarations = newScopedDecls)(b.pos, b.info, b.errT) - }) - - val finalMethod = Method(pres = method.pres map t.exp, posts = method.posts map t.exp, body = newBody, formalArgs = method.formalArgs map t.liftVarDecl, formalReturns = method.formalReturns map t.liftVarDecl, name = method.idndef.name)(method.pos._1) - - t.getMembers().addOne(method.idndef.name, finalMethod) - - finalMethod - } - override def translateMemberSignature(t: Translator): Member = { - Method(method.idndef.name, method.formalArgs map t.liftVarDecl, method.formalReturns map t.liftVarDecl, null, null, null)(method.pos._1) - } - */ } case class POldCall(rets: Seq[PIdnUse], lbl:PIdnUse, method: PIdnUse, args:Seq[PExp])(val pos: (Position, Position)) extends PExtender with PStmt { @@ -212,11 +114,13 @@ case class POldCall(rets: Seq[PIdnUse], lbl:PIdnUse, method: PIdnUse, args:Seq[P args.foreach(a => t.checkTopTyped(a,None) ) + rets.foreach(r => + t.checkTopTyped(r, None)) None } override def translateStmt(t: Translator): Stmt = { - //OldCall(rets.map(r => LocalVar(r.name, t.ttyp(r.typ))(t.liftPos(r))), Label(lbl.name, Seq() map t.exp)(t.liftPos(lbl)),t.exp(exp))(t.liftPos(this)) - OldCall(MethodCall(method.name, args map t.exp, rets.map(r => LocalVar(r.name,t.ttyp(r.typ))(t.liftPos(r))))(t.liftPos(method), NoInfo, NoTrafos), Label(lbl.name, Seq())(t.liftPos(lbl)))(t.liftPos(this)) + OldCall(method.name, args map t.exp, (rets map t.exp).asInstanceOf[Seq[LocalVar]], Label(lbl.name, Seq())(t.liftPos(lbl)))(t.liftPos(this)) + } } \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index 113575030..223d83b8d 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -6,10 +6,10 @@ import org.jgrapht.Graph import org.jgrapht.graph.{DefaultDirectedGraph, DefaultEdge} import viper.silver.ast._ import viper.silver.ast.utility.rewriter.Traverse -import viper.silver.ast.utility.{Expressions, ViperStrategy} -import viper.silver.parser.FastParserCompanion.{LW, LeadingWhitespace, whitespace} +import viper.silver.ast.utility.ViperStrategy +import viper.silver.parser.FastParserCompanion.whitespace import viper.silver.parser._ -import viper.silver.plugin.standard.reasoning.analysis.{VarAnalysisGraph, VarAnalysisSet} +import viper.silver.plugin.standard.reasoning.analysis.{SetGraphComparison, VarAnalysisGraph} import viper.silver.plugin.{ParserPluginTemplate, SilverPlugin} import viper.silver.verifier._ @@ -19,7 +19,7 @@ import scala.collection.mutable class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, @unused logger: ch.qos.logback.classic.Logger, @unused config: viper.silver.frontend.SilFrontendConfig, - fp: FastParser) extends SilverPlugin with ParserPluginTemplate with VarAnalysisSet with BeforeVerifyHelper { + fp: FastParser) extends SilverPlugin with ParserPluginTemplate with SetGraphComparison with BeforeVerifyHelper { import fp.{FP, ParserExtension, block, exp, idndef, idnuse, keyword, trigger, typ, formalArgList, pre, post, LeadingWhitespace, oldLabel, parens} import viper.silver.parser.FastParserCompanion.LW @@ -28,97 +28,101 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, override def reportErrorWithMsg(error: AbstractError): Unit = reportError(error) /** Parser for existential elimination statements. */ - def existential_elim[_: P]: P[PExistentialElim] = { + def existential_elim[_: P]: P[PExistentialElim] = FP(keyword("obtain") ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ keyword("where") ~/ trigger.rep ~/ exp).map { case (pos, (varList, t, e)) => PExistentialElim(varList.map { case (id, typ) => PLocalVarDecl(id, typ, None)(e.pos) }, t, e)(pos) } - } - /** Parser for universal introduction statements. */ def universal_intro[_: P]: P[PUniversalIntro] = FP(keyword("prove") ~/ keyword("forall") ~/ (idndef ~ ":" ~ typ).rep(sep = ",") ~/ trigger.rep(sep = ",") ~/ keyword("assuming") ~/ exp ~/ keyword("implies") ~/ exp ~/ block).map { case (pos, (varList, triggers, e1, e2, b)) => PUniversalIntro(varList.map { case (id, typ) => PLocalVarDecl(id, typ, None)(e1.pos) }, triggers, e1, e2, b)(pos) } + /** Parser for new influence by condition */ def influenced_by[_: P]: P[PFlowAnnotation] = P(keyword("influenced") ~/ (influenced_by_var | influenced_by_heap)) - def influenced_by_var[_: P]: P[PFlowAnnotation] = { - FP(idnuse ~/ keyword("by") ~ "{" ~/ (heap_then_vars | vars_then_opt_heap) ~/ "}").map { - case (pos, (v_idnuse: PExp, (varList: Seq[PExp], None))) => - PFlowAnnotation(PVar(v_idnuse)(v_idnuse.pos), varList.map(vl => PVar(vl)(vl.pos)))(pos) - case (pos, (v_idnuse: PExp, varList: Option[Seq[PExp]])) => - PFlowAnnotation(PVar(v_idnuse)(v_idnuse.pos), (varList.getOrElse(Seq()).map(vl => PVar(vl)(vl.pos))) ++ Seq(PHeap()(v_idnuse.pos)))(pos) - case (pos, (v_idnuse: PExp, (varList1: Seq[PExp], varList2: Some[Seq[PExp]]))) => - PFlowAnnotation(PVar(v_idnuse)(v_idnuse.pos), ((varList1 ++ varList2.getOrElse(Seq())).map(vl => PVar(vl)(vl.pos))) ++ Seq(PHeap()(v_idnuse.pos)))(pos) - } + def influenced_by_var[_: P]: P[PFlowAnnotation] = FP(idnuse ~/ keyword("by") ~ "{" ~/ (heap_then_vars | vars_then_opt_heap) ~/ "}").map { + case (pos, (v_idnuse: PExp, (varList: Seq[PExp], None))) => + PFlowAnnotation(PVar(v_idnuse)(v_idnuse.pos), varList.map(vl => PVar(vl)(vl.pos)))(pos) + case (pos, (v_idnuse: PExp, varList: Option[Seq[PExp]])) => + PFlowAnnotation(PVar(v_idnuse)(v_idnuse.pos), (varList.getOrElse(Seq()).map(vl => PVar(vl)(vl.pos))) ++ Seq(PHeap()(v_idnuse.pos)))(pos) + case (pos, (v_idnuse: PExp, (varList1: Seq[PExp], varList2: Some[Seq[PExp]]))) => + PFlowAnnotation(PVar(v_idnuse)(v_idnuse.pos), ((varList1 ++ varList2.getOrElse(Seq())).map(vl => PVar(vl)(vl.pos))) ++ Seq(PHeap()(v_idnuse.pos)))(pos) + } - def vars_then_opt_heap[_: P]: P[(Seq[PExp], Option[Seq[PExp]])] = { - P(idnuse.rep(sep = ",") ~/ ("," ~/ keyword("heap") ~/ ("," ~/ idnuse.rep(sep = ",")).?).?.map { - /** If there is no heap keyword */ - case None => None + def vars_then_opt_heap[_: P]: P[(Seq[PExp], Option[Seq[PExp]])] = P(idnuse.rep(sep = ",") ~/ ("," ~/ keyword("heap") ~/ ("," ~/ idnuse.rep(sep = ",")).?).?.map { + /** If there is no heap keyword */ + case None => None - /** If there is the heap keyword but no further variables */ - case Some(None) => Some(Seq()) + /** If there is the heap keyword but no further variables */ + case Some(None) => Some(Seq()) - /** If there is the heap keyword and additionally further variables */ - case Some(Some(varList)) => Some(varList) + /** If there is the heap keyword and additionally further variables */ + case Some(Some(varList)) => Some(varList) - }) - } + }) - def heap_then_vars[_: P]: P[Option[Seq[PExp]]] = { - P(keyword("heap") ~/ ("," ~/ idnuse.rep(sep = ",")).?) - } + def heap_then_vars[_: P]: P[Option[Seq[PExp]]] = P(keyword("heap") ~/ ("," ~/ idnuse.rep(sep = ",")).?) - def influenced_by_heap[_: P]: P[PFlowAnnotation] = { - FP(keyword("heap") ~/ keyword("by") ~ "{" ~/ (heap_then_vars | vars_then_opt_heap) ~/ "}").map { - case (pos, (varList: Seq[PExp], None)) => - reportError(ConsistencyError("The heap should always be influenced by the heap.", SourcePosition(pos._1.file, pos._1, pos._2))) - PFlowAnnotation(PHeap()(pos),varList.map(vl => PVar(vl)(vl.pos)))(pos) - case (pos, varList: Option[Seq[PExp]]) => - PFlowAnnotation(PHeap()(pos), (varList.getOrElse(Seq()).map(vl => PVar(vl)(vl.pos))) ++ Seq(PHeap()(pos)))(pos) - case (pos, (varList1: Seq[PExp], varList2: Some[Seq[PExp]])) => - PFlowAnnotation(PHeap()(pos), (varList1 ++ varList2.getOrElse(Seq())).map(vl => PVar(vl)(vl.pos)) ++ Seq(PHeap()(pos)))(pos) - } + def influenced_by_heap[_: P]: P[PFlowAnnotation] = FP(keyword("heap") ~/ keyword("by") ~ "{" ~/ (heap_then_vars | vars_then_opt_heap) ~/ "}").map { + case (pos, (varList: Seq[PExp], None)) => + reportError(ConsistencyError("The heap should always be influenced by the heap.", SourcePosition(pos._1.file, pos._1, pos._2))) + PFlowAnnotation(PHeap()(pos),varList.map(vl => PVar(vl)(vl.pos)))(pos) + case (pos, varList: Option[Seq[PExp]]) => + PFlowAnnotation(PHeap()(pos), (varList.getOrElse(Seq()).map(vl => PVar(vl)(vl.pos))) ++ Seq(PHeap()(pos)))(pos) + case (pos, (varList1: Seq[PExp], varList2: Some[Seq[PExp]])) => + PFlowAnnotation(PHeap()(pos), (varList1 ++ varList2.getOrElse(Seq())).map(vl => PVar(vl)(vl.pos)) ++ Seq(PHeap()(pos)))(pos) } - def lemma[_:P]: P[PLemma] = { - FP(keyword("isLemma")).map { case (pos,()) => PLemma()(pos)} - } + /** parser for lemma annotation */ + def lemma[_: P]: P[PLemma] = FP(keyword("isLemma")).map { case (pos,()) => PLemma()(pos)} - def oldCall[_:P]: P[POldCall] = { - FP((idnuse.rep(sep = ",") ~ ":=").? ~ keyword("oldCall") ~ "[" ~ oldLabel ~ "]" ~ "(" ~ idnuse ~ parens(exp.rep(sep = ",")) ~ ")").map { - case (pos, (None, lbl, method, args)) => - POldCall(Nil, lbl, method, args)(pos) - case (pos, (Some(targets), lbl, method, args)) => - POldCall(targets, lbl, method, args)(pos) - } + /** parser for oldCall statement */ + def oldCall[_: P]: P[POldCall] = FP((idnuse.rep(sep = ",") ~ ":=").? ~ keyword("oldCall") ~ "[" ~ oldLabel ~ "]" ~ "(" ~ idnuse ~ parens(exp.rep(sep = ",")) ~ ")").map { + case (pos, (None, lbl, method, args)) => + POldCall(Nil, lbl, method, args)(pos) + case (pos, (Some(targets), lbl, method, args)) => + POldCall(targets, lbl, method, args)(pos) } + /** Add existential elimination and universal introduction to the parser. */ override def beforeParse(input: String, isImported: Boolean): String = { - // Add new keyword + /** keywords for existential elimination and universal introduction */ ParserExtension.addNewKeywords(Set[String]("obtain", "where", "prove", "forall", "assuming", "implies")) + + /** keywords for flow annotation and therefore modular flow analysis */ ParserExtension.addNewKeywords(Set[String]("influenced", "by", "heap")) + + /** keyword to declare a lemma and to call the lemma in an old context*/ ParserExtension.addNewKeywords(Set[String]("isLemma")) ParserExtension.addNewKeywords(Set[String]("oldCall")) + + /** adding existential elimination and universal introduction to the parser */ ParserExtension.addNewStmtAtEnd(existential_elim(_)) ParserExtension.addNewStmtAtEnd(universal_intro(_)) - // Add new parser to the postcondition + + /** add influenced by flow annotation to as a postcondition */ ParserExtension.addNewPostCondition(influenced_by(_)) + /** add lemma as an annotation either as a pre- or a postcondition */ ParserExtension.addNewPreCondition(lemma(_)) ParserExtension.addNewPostCondition(lemma(_)) - ParserExtension.addNewStmtAtEnd(oldCall(_)) + /** add the oldCall as a new stmt */ + ParserExtension.addNewStmtAtStart(oldCall(_)) input } override def beforeVerify(input: Program): Program = { + + /** for evaluation purposes */ + //val begin_time = System.currentTimeMillis() + val usedNames: mutable.Set[String] = collection.mutable.Set(input.transitiveScopedDecls.map(_.name): _*) /** check that lemma terminates (has a decreases clause) and that it is pure */ @@ -128,10 +132,45 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, checkInfluencedBy(input, reportError) - ViperStrategy.Slim({ - case o@OldCall(mc,lbl) => + /** method call to compare the analysis of the set-approach vs. the graph approach */ + //compareGraphSet(input, reportError) + + + val newAst: Program = ViperStrategy.Slim({ + + /** remove the influenced by postconditions. + * remove isLemma */ + case m: Method => + + + var postconds: Seq[Exp] = Seq() + m.posts.foreach { + case _: FlowAnnotation => + postconds = postconds + case _: Lemma => + postconds = postconds + case s@_ => + postconds = postconds ++ Seq(s) + } + var preconds: Seq[Exp] = Seq() + m.pres.foreach { + case _: Lemma => + preconds = preconds + case s@_ => + preconds = preconds ++ Seq(s) + } + val newMethod = + if (postconds != m.posts || preconds != m.pres) { + m.copy(pres = preconds, posts = postconds)(m.pos, m.info, m.errT) + } else { + m + } + + newMethod + + case o@OldCall(methodName, args, rets, lbl) => /** check whether called method is a lemma */ - val currmethod = input.findMethod(mc.methodName) + val currmethod = input.findMethod(methodName) var isLemma:Boolean = currmethod.pres.exists(p => p.isInstanceOf[Lemma]) isLemma = isLemma || currmethod.posts.exists(p => p.isInstanceOf[Lemma]) @@ -139,15 +178,60 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, reportError(ConsistencyError(s"method ${currmethod.name} called in old context must be lemma", o.pos)) } + var new_pres: Seq[Exp] = Seq() + var new_posts: Seq[Exp] = Seq() + var new_v_map: Seq[(LocalVarDecl, Exp)] = + (args zip currmethod.formalArgs).map(zipped => { + val formal_a: LocalVarDecl = zipped._2 + val arg_exp: Exp = zipped._1 + formal_a -> arg_exp + }) + new_v_map ++= + (rets zip currmethod.formalReturns).map(zipped => { + val formal_r: LocalVarDecl = zipped._2 + val r: LocalVar = zipped._1 + formal_r -> r + }) + /** replace all variables in precondition with fresh variables */ + currmethod.pres.foreach { + case Lemma() => () + case p => + new_pres ++= Seq(applySubstitutionWithExp(new_v_map, p)) + } + + /** replace all variables in postcondition with fresh variables */ + currmethod.posts.foreach { + case Lemma() => () + case p => + new_posts ++= Seq(applySubstitutionWithExp(new_v_map, p)) + } + + /** create new variable declarations to havoc the lhs of the oldCall */ + var new_v_decls: Seq[LocalVarDecl] = Seq() + var rTov: Map[LocalVar,LocalVarDecl] = Map() + for (r <- rets) { + val new_v = LocalVarDecl(uniqueName(".v", usedNames),r.typ)(r.pos) + new_v_decls = new_v_decls ++ Seq(new_v) + rTov += (r -> new_v) + } + Seqn( - currmethod.pres.map(p => + new_pres.map(p => Assert(LabelledOld(p, lbl.name)(p.pos))(o.pos) - ) ++ - currmethod.posts.map(p => - Assume(LabelledOld(p, lbl.name)(p.pos))(o.pos) + ) + ++ + + rets.map(r => { + LocalVarAssign(r,rTov(r).localVar)(o.pos) + }) + ++ + + + new_posts.map(p => + Inhale(LabelledOld(p, lbl.name)(p.pos))(o.pos) ), - Seq() + new_v_decls )(o.pos) @@ -167,34 +251,6 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, Seq() )(e.pos) - /** remove the influenced by postconditions. - * remove isLemma*/ - case m: Method => - var postconds: Seq[Exp] = Seq() - m.posts.foreach { - case _: FlowAnnotation => - postconds = postconds - case _: Lemma => - postconds = postconds - case s@_ => - postconds = postconds ++ Seq(s) - } - var preconds: Seq[Exp] = Seq() - m.pres.foreach { - case _: Lemma => - preconds = preconds - case s@_ => - preconds = preconds ++ Seq(s) - } - - val newMethod = - if (postconds != m.posts || preconds != m.pres) { - m.copy(pres = preconds, posts = postconds)(m.pos, m.info, m.errT) - } else { - m - } - newMethod - case u@UniversalIntro(v, trigs, exp1, exp2, blk) => val boolvar = LocalVarDecl(uniqueName("b", usedNames), Bool)(exp1.pos) @@ -207,7 +263,7 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, } })) - /** Variables declared in the universal introduction statement are tainted */ + /** Qunatified variables in the universal introduction statement are tainted */ val tainted: Set[LocalVarDecl] = v.toSet @@ -218,11 +274,13 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, val graph_analysis: VarAnalysisGraph = VarAnalysisGraph(input, reportError) - /** create graph with vars that are in scope */ + /** create graph with vars that are in scope only outside of the universal introduction code block including the qunatified variables*/ vars_outside_blk --= mutable.Set(u.transitiveScopedDecls: _*) vars_outside_blk ++= v + val graph: Graph[LocalVarDecl, DefaultEdge] = new DefaultDirectedGraph[LocalVarDecl, DefaultEdge](classOf[DefaultEdge]) + /** Map that contains all variables where the key is represents the variables final value and the value the variables initial value before a statement. */ var allVertices: Map[LocalVarDecl, LocalVarDecl] = Map[LocalVarDecl, LocalVarDecl]() /** add heap variables to vertices */ @@ -234,35 +292,40 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, val v_init = graph_analysis.createInitialVertex(v_decl) allVertices += (v_decl -> v_init) + /** add all variable to the graph */ graph.addVertex(v_init) graph.addVertex(v_decl) } }) + /** * get all variables that are assigned to inside the block and take intersection with universal introduction - * variables. If they are contained throw error since these variables should be immutable + * variables. If they are contained throw error since quantified variables should be immutable */ - val written_vars: Option[Set[LocalVarDecl]] = graph_analysis.writtenTo(allVertices ,blk) + val written_vars: Option[Set[LocalVarDecl]] = graph_analysis.getModifiedVars(allVertices ,blk) checkReassigned(written_vars, v, reportError, u) - graph_analysis.executeTaintedGraphAnalysis(graph, tainted, blk, allVertices, u) + + /** execute modular flow analysis using graphs for the universal introduction statement */ + graph_analysis.executeTaintedGraphAnalysis(tainted, blk, allVertices, u) /** * SET VERSION */ - /* + /* val tainted_decls: Set[Declaration] = tainted.map(t => t.asInstanceOf[Declaration]) executeTaintedSetAnalysis(tainted_decls, vars_outside_blk, blk, u, reportError) - */ + */ + + /** Translate the new syntax into Viper language */ val (new_v_map, new_exp1) = substituteWithFreshVars(v, exp1, usedNames) val new_exp2 = applySubstitution(new_v_map, exp2) val arb_vars = new_v_map.map(vars => vars._2) val new_trigs = trigs.map(t => Trigger(t.exps.map(e1 => applySubstitution(new_v_map, e1)))(t.pos)) - // label should also be in used Names => can also use uniqueName function val lbl = uniqueName("l", usedNames) @@ -285,6 +348,14 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, Seq(boolvar) ++ v )(exp1.pos) - }, Traverse.TopDown).execute(input) + }, Traverse.TopDown).execute[Program](input) + /** for evaluation purposes */ + /* + val end_time = System.currentTimeMillis() + println("--------------------------------------------------------------------------") + println("beforeVerify time: " + (end_time - begin_time) + "ms") + println("--------------------------------------------------------------------------") + */ + newAst } } \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala new file mode 100644 index 000000000..28e2b5368 --- /dev/null +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala @@ -0,0 +1,146 @@ +package viper.silver.plugin.standard.reasoning.analysis + +import org.jgrapht.Graph +import org.jgrapht.graph.DefaultEdge +import viper.silver.ast.{Declaration, LocalVarDecl, Program, Stmt} +import viper.silver.verifier.AbstractError + +import scala.jdk.CollectionConverters.CollectionHasAsScala + +trait SetGraphComparison extends VarAnalysisSet { + def computeSet(v: Declaration, blk: Stmt): Set[LocalVarDecl] = { + get_tainted_vars_stmt(Set(v), blk).map(v => v.asInstanceOf[LocalVarDecl]) + } + + def computeGraph(graph_analysis: VarAnalysisGraph, vars: Set[LocalVarDecl], blk: Stmt): (Graph[LocalVarDecl, DefaultEdge]/*, Map[LocalVarDecl,LocalVarDecl]*/) = { + + var allVertices: Map[LocalVarDecl, LocalVarDecl] = Map[LocalVarDecl, LocalVarDecl]() + + /** add heap variables to vertices */ + allVertices += (graph_analysis.heap_vertex -> graph_analysis.createInitialVertex(graph_analysis.heap_vertex)) + + vars.foreach(v => { + val v_decl = v + val v_init = graph_analysis.createInitialVertex(v_decl) + allVertices += (v_decl -> v_init) + + }) + graph_analysis.compute_graph(blk, allVertices) + } + + def compareGraphSet(prog: Program, reportError: AbstractError => Unit): Unit = { + + /** + * SETS + */ + val beginTimeSets = System.currentTimeMillis() + var setForMethods: Map[String, Map[LocalVarDecl, Set[LocalVarDecl]]] = Map() + prog.methods.foreach(m => { + var vToSet: Map[LocalVarDecl, Set[LocalVarDecl]] = Map() + val allParameters: Set[LocalVarDecl] = m.formalArgs.toSet.concat(m.formalReturns.toSet) + m.formalArgs.foreach(arg => { + vToSet += (arg -> computeSet(arg,m.bodyOrAssumeFalse).intersect(allParameters)) + }) + setForMethods += (m.name -> vToSet) + + }) + + val endTimeSets = System.currentTimeMillis() + val totalTimeSets = endTimeSets - beginTimeSets + println("Time for Set analysis: " + totalTimeSets + "ms") + /* + val beginTimeSets = System.currentTimeMillis() + var vToSet: Map[LocalVarDecl,Set[LocalVarDecl]] = Map() + vars.foreach(v => { + vToSet += (v.asInstanceOf[LocalVarDecl] -> (computeSet(v,blk).intersect(vars.map(d => d.asInstanceOf[LocalVarDecl])))) + }) + val endTimeSets = System.currentTimeMillis() + val totalTimeSets = endTimeSets - beginTimeSets + + + println("Time for Set analysis: " + totalTimeSets + "ms") + + */ + + + /** + * GRAPHS + */ + + val beginTimeGraphs = System.currentTimeMillis() + val graph_analysis : VarAnalysisGraph = VarAnalysisGraph(prog, reportError) + var graphForMethods: Map[String, Graph[LocalVarDecl,DefaultEdge]] = Map[String, Graph[LocalVarDecl,DefaultEdge]]() + prog.methods.foreach(m => { + graphForMethods += (m.name -> computeGraph(graph_analysis, m.formalArgs.toSet.concat(m.formalReturns.toSet), m.bodyOrAssumeFalse)) + }) + + val endTimeGraphs = System.currentTimeMillis() + val totalTimeGraphs = endTimeGraphs - beginTimeGraphs + println("Time for Graph analysis: " + totalTimeGraphs + "ms") + println("--------------------------------------------------------------------------") + /* + val beginTimeGraphs = System.currentTimeMillis() + val graph_analysis: VarAnalysisGraph = VarAnalysisGraph(prog, reportError) + val (graph: Graph[LocalVarDecl,DefaultEdge],_) = computeGraph(graph_analysis, vars,blk,prog, reportError) + val endTimeGraphs = System.currentTimeMillis() + val totalTimeGraphs = endTimeGraphs - beginTimeGraphs + + println("Time for Graph analysis: " + totalTimeGraphs + "ms") + println("--------------------------------------------------------------------------") + + */ + + + /** compare for each variable */ + graphForMethods.foreach((mg) => { + val methodname = mg._1 + val graph = mg._2 + + val set = setForMethods(methodname) + + prog.findMethod(methodname).formalArgs.foreach(arg => { + val out_edges = graph.outgoingEdgesOf(graph_analysis.createInitialVertex(arg)).asScala.toSet + + var graph_vars: Set[LocalVarDecl] = Set() + out_edges.foreach(e => { + graph_vars += graph.getEdgeTarget(e) + }) + + val set_vars: Set[LocalVarDecl] = set(arg) + + if ((graph_vars - arg).equals(set_vars - arg)) { + //println("SET AND GRAPH OF " + arg + " EQUAL") + } else { + println("SET AND GRAPH OF " + arg + " NOT EQUAL") + println("set: " + set_vars) + println("graph: " + graph_vars) + println(graph_analysis.createDOT(graph)) + println("method:\n" + methodname) + } + }) + }) + /* + vars.foreach(v => { + val out_edges = graph.outgoingEdgesOf(graph_analysis.createInitialVertex(v.asInstanceOf[LocalVarDecl])).asScala.toSet + + var graph_vars: Set[LocalVarDecl] = Set() + out_edges.foreach(e => { + graph_vars += graph.getEdgeTarget(e) + }) + + val set_vars: Set[LocalVarDecl] = vToSet(v.asInstanceOf[LocalVarDecl]) + + if ((graph_vars - v.asInstanceOf[LocalVarDecl]).equals(set_vars - v.asInstanceOf[LocalVarDecl])) { + println("SET AND GRAPH OF " + v + " EQUAL") + } else { + println("SET AND GRAPH OF " + v + " NOT EQUAL") + println("set: " + set_vars) + println("graph: " + graph_vars) + println(graph_analysis.createDOT(graph)) + println("method:\n" + m) + } + + }) + */ + } +} diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala index d847eea6c..989368df7 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala @@ -2,16 +2,14 @@ package viper.silver.plugin.standard.reasoning.analysis import org.jgrapht.Graph import org.jgrapht.graph.{AbstractBaseGraph, DefaultDirectedGraph, DefaultEdge} -import viper.silver.ast.{AccessPredicate, Apply, Assert, Assume, BinExp, CurrentPerm, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, Fold, ForPerm, FuncApp, Goto, If, Inhale, Int, Label, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Package, Perm, Program, Ref, Seqn, Stmt, UnExp, Unfold, While} +import viper.silver.ast.{AccessPredicate, Apply, Assert, Assume, BinExp, CurrentPerm, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, Fold, ForPerm, FuncApp, Goto, If, Inhale, Int, Label, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Package, Program, Ref, Seqn, Stmt, UnExp, Unfold, While} import viper.silver.plugin.standard.reasoning.{FlowAnnotation, OldCall, Var} -//import viper.silver.plugin.standard.reasoning.{ExistentialElim, FlowAnnotationHeap, FlowAnnotationHeapHeapArg, FlowAnnotationVar, FlowAnnotationVarHeapArg, UniversalIntro} import viper.silver.plugin.standard.reasoning.{ExistentialElim, UniversalIntro} import viper.silver.verifier.{AbstractError, ConsistencyError} import java.io.StringWriter import scala.jdk.CollectionConverters._ -import scala.util.control.Breaks.break @@ -23,11 +21,11 @@ case class VarAnalysisGraph(prog: Program, val heap_vertex: LocalVarDecl = LocalVarDecl(".heap", Ref)() - def executeTaintedGraphAnalysis(graph1: Graph[LocalVarDecl,DefaultEdge], tainted: Set[LocalVarDecl], blk: Seqn, allVertices: Map[LocalVarDecl, LocalVarDecl], u: UniversalIntro): Unit = { - - - val graph = compute_graph(graph1, blk, allVertices) + /** execute the information flow analysis with graphs. + * When executed on the universal introduction statement the tainted variables are simply the quantified variables */ + def executeTaintedGraphAnalysis(tainted: Set[LocalVarDecl], blk: Seqn, allVertices: Map[LocalVarDecl, LocalVarDecl], u: UniversalIntro): Unit = { + val graph = compute_graph(blk, allVertices) var noEdges: Boolean = true var badEdges = Set[DefaultEdge]() @@ -45,9 +43,9 @@ case class VarAnalysisGraph(prog: Program, tainted_vars = tainted_vars + graph.getEdgeTarget(e) } }) - val problem_vars: String = tainted_vars.mkString(", ") - val problem_pos: String = tainted_vars.map(vs => vs.pos).mkString(", ") - reportErrorWithMsg(ConsistencyError("Universal introduction variable might have been assigned to variable " + problem_vars + " at positions (" + problem_pos + "), defined outside of the block", u.pos)) + val tainted_vars_sorted: List[LocalVarDecl] = tainted_vars.toList.sortWith(_.name < _.name) + val problem_vars: String = tainted_vars_sorted.mkString(", ") + reportErrorWithMsg(ConsistencyError("Universal introduction variable might have been assigned to variable " + problem_vars + ", defined outside of the block", u.pos)) } } @@ -95,7 +93,7 @@ case class VarAnalysisGraph(prog: Program, * @param vertices the vertices representing variables which should be checked * @return graph */ - def addMissingEdges(graph: Graph[LocalVarDecl,DefaultEdge], vertices:Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl, DefaultEdge] = { + def addIdentityEdges(graph: Graph[LocalVarDecl,DefaultEdge], vertices:Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl, DefaultEdge] = { for ((v,v_init)<-vertices) { if (graph.incomingEdgesOf(v).isEmpty) { @@ -106,6 +104,7 @@ case class VarAnalysisGraph(prog: Program, } /** + * for debugging purposes * @param graph graph that should be translated to DOT-language * @return String that is the graph in DOT-language * @@ -182,7 +181,11 @@ case class VarAnalysisGraph(prog: Program, case FieldAccess(v,_) => - getVarsFromExpr(graph, v) + val allVars = vars ++ getVarsFromExpr(graph,v) + if(!allVars.contains(heap_vertex)) + allVars + heap_vertex + else + allVars case AccessPredicate(access, _) => /** Should only be the case in e.g.an inhale or an exhale statement */ @@ -209,7 +212,7 @@ case class VarAnalysisGraph(prog: Program, } /** - * takes to graphs and returns a new graph containing the union of the edges of both input graphs. Both graphs should contain the same vertices! + * takes two graphs and returns a new graph containing the union of the edges of both input graphs. Both graphs should contain the same vertices! * @param graph1 * @param graph2 * @return graph @@ -225,7 +228,8 @@ case class VarAnalysisGraph(prog: Program, } } } else { - /** TODO: Should error be thrown? Should not happen */ + /* should not happen */ + () } new_graph } @@ -250,15 +254,18 @@ case class VarAnalysisGraph(prog: Program, new_graph.addEdge(src, graph2.getEdgeTarget(e2), new DefaultEdge) } } else { - /** TODO: Should technically not happen */ + /* should not happen */ + () } } new_graph } - - def compute_graph(graph: Graph[LocalVarDecl,DefaultEdge], stmt: Stmt, vertices: Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl,DefaultEdge] = { + /** creates a graph based on the statement + * edge is influenced by relation: source influences target + * vertices are all variables in scope*/ + def compute_graph(stmt: Stmt, vertices: Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl,DefaultEdge] = { stmt match { case Seqn(ss, scopedSeqnDeclarations) => var allVertices: Map[LocalVarDecl,LocalVarDecl] = vertices @@ -271,8 +278,7 @@ case class VarAnalysisGraph(prog: Program, } var new_graph: Graph[LocalVarDecl, DefaultEdge] = createIdentityGraph(allVertices) for (s <- ss) { - val graph_copy = copyGraph(new_graph) - val comp_graph = compute_graph(graph_copy, s, allVertices) + val comp_graph = compute_graph(s, allVertices) new_graph = mergeGraphs(new_graph, comp_graph, allVertices) } @@ -291,10 +297,10 @@ case class VarAnalysisGraph(prog: Program, val id_graph = createIdentityGraph(vertices) val expr_vars = getVarsFromExpr(id_graph, cond) val cond_graph = copyGraph(id_graph) - val thn_graph = compute_graph(copyGraph(id_graph), thn, vertices) - val els_graph = compute_graph(copyGraph(id_graph), els, vertices) - val writtenToThn = writtenTo(vertices, thn).getOrElse(Set()) - val writtenToEls = writtenTo(vertices, els).getOrElse(Set()) + val thn_graph = compute_graph(thn, vertices) + val els_graph = compute_graph(els, vertices) + val writtenToThn = getModifiedVars(vertices, thn).getOrElse(Set()) + val writtenToEls = getModifiedVars(vertices, els).getOrElse(Set()) val allWrittenTo = writtenToThn ++ writtenToEls for (w <- allWrittenTo) { if (cond_graph.containsVertex(w)) { @@ -311,30 +317,29 @@ case class VarAnalysisGraph(prog: Program, val res_graph = unionEdges(cond_graph, thn_els_graph) res_graph - case w@While(cond, _, body) => - val graph_copy: Graph[LocalVarDecl, DefaultEdge] = copyGraph(graph) + case While(cond, _, body) => /** analyse one iteration of the while loop */ - var new_graph: Graph[LocalVarDecl, DefaultEdge] = compute_graph(graph_copy, If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), vertices) - new_graph = mergeGraphs(graph_copy, new_graph, vertices) - - /** check whether the edges are equal. - * First check whether both edge sets have the same size - * then go through each edge and check whether it also exists in the new graph */ - var edges_equal: Boolean = true - val equal_size: Boolean = graph.edgeSet().size().equals(new_graph.edgeSet().size()) - if (equal_size && new_graph.vertexSet().equals(graph.vertexSet())) { - for (e1: DefaultEdge <- new_graph.edgeSet().asScala.toSet) { - if (graph.getEdge(new_graph.getEdgeSource(e1), new_graph.getEdgeTarget(e1)) == null) { - edges_equal = false - compute_graph(new_graph, w, vertices) - break() + val one_iter_graph: Graph[LocalVarDecl, DefaultEdge] = compute_graph(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), vertices) + + var edges_equal: Boolean = false + var merge_graph = copyGraph(one_iter_graph) + while(!edges_equal) { + val last_iter_graph = copyGraph(merge_graph) + merge_graph = mergeGraphs(merge_graph, one_iter_graph, vertices) + val equal_size: Boolean = last_iter_graph.edgeSet().size().equals(merge_graph.edgeSet().size()) + if (equal_size && last_iter_graph.vertexSet().equals(merge_graph.vertexSet())) { + for (e1: DefaultEdge <- last_iter_graph.edgeSet().asScala.toSet) { + if (merge_graph.getEdge(last_iter_graph.getEdgeSource(e1), last_iter_graph.getEdgeTarget(e1)) == null) { + edges_equal = false + + } else { + edges_equal = true + } } } - graph - } else { - compute_graph(new_graph, w, vertices) } + merge_graph case LocalVarAssign(lhs,rhs) => var new_graph: Graph[LocalVarDecl,DefaultEdge] = createEmptyGraph(vertices) @@ -354,37 +359,25 @@ case class VarAnalysisGraph(prog: Program, /** Since variables that are not assigned to should have an edge from their initial value to their 'end'-value */ val vert_wout_lhs = vertices - lhs_decl - new_graph = addMissingEdges(new_graph, vert_wout_lhs) + new_graph = addIdentityEdges(new_graph, vert_wout_lhs) new_graph - case Inhale(exp) => expInfluencesAll(exp, graph, vertices) - /* + case Inhale(exp) => val id_graph = createIdentityGraph(vertices) - val inhale_vars = getVarsFromExpr(graph, exp) - inhale_vars.foreach(v => { - //val init_v = createInitialVertex(v) - val init_v = vertices(v) - - //id_graph.addEdge(init_v, heap_vertex, new DefaultEdge) - vertices.keySet.foreach(k => { - id_graph.addEdge(init_v, k, new DefaultEdge) - println(s"edge from ${init_v} to ${k}") - }) - - }) - id_graph - */ + expInfluencesAllVertices(exp, id_graph, vertices) /** same as inhale */ - case Assume(exp) => expInfluencesAll(exp, graph, vertices) + case Assume(exp) => + val id_graph = createIdentityGraph(vertices) + expInfluencesAllVertices(exp, id_graph, vertices) case Exhale(exp) => val id_graph = createIdentityGraph(vertices) if (exp.isPure) { - graph + id_graph } else { - val exhale_vars = getVarsFromExpr(graph, exp) + val exhale_vars = getVarsFromExpr(id_graph, exp) exhale_vars.foreach(v => { if (v.typ == Ref) { val init_v = createInitialVertex(v) @@ -395,13 +388,14 @@ case class VarAnalysisGraph(prog: Program, } + case Assert(_) => createIdentityGraph(vertices) + case Label(_, _) => - graph + createIdentityGraph(vertices) case MethodCall(methodName, args, targets) => val met = prog.findMethod(methodName) - /** create graph from each variable in each expression to the according method variable */ val methodcall_graph: Graph[LocalVarDecl, DefaultEdge] = createEmptyGraph(vertices) createInfluencedByGraph(methodcall_graph,vertices,args,targets,met.formalArgs, met.formalReturns,met.posts) @@ -409,7 +403,7 @@ case class VarAnalysisGraph(prog: Program, case FieldAssign(_, rhs) => val id_graph = createIdentityGraph(vertices) - val rhs_vars = getVarsFromExpr(graph, rhs) + val rhs_vars = getVarsFromExpr(id_graph, rhs) rhs_vars.foreach(v => { /** Edge from .init_heap to heap does not have to be added since it exists anyways */ if (v.equals(heap_vertex)) { @@ -421,22 +415,23 @@ case class VarAnalysisGraph(prog: Program, }) id_graph - case ExistentialElim(_,_,_) => graph + /** TODO: technically not implemented correctly */ + case ExistentialElim(_,_,_) => + createIdentityGraph(vertices) case UniversalIntro(varList,_,_,_,blk) => val new_vertices: Map[LocalVarDecl, LocalVarDecl] = vertices ++ varList.map(v => (v -> createInitialVertex(v))) - val new_graph = compute_graph(graph,blk,new_vertices) + val new_graph = compute_graph(blk,new_vertices) varList.foreach(v => { new_graph.removeVertex(v) new_graph.removeVertex(new_vertices(v)) }) new_graph - case Assert(_) => graph case Fold(acc) => val id_graph = createIdentityGraph(vertices) - val vars = getVarsFromExpr(graph, acc) + val vars = getVarsFromExpr(id_graph, acc) vars.foreach(v => { id_graph.addEdge(vertices(v), heap_vertex, new DefaultEdge) }) @@ -444,7 +439,7 @@ case class VarAnalysisGraph(prog: Program, case Unfold(acc) => val id_graph = createIdentityGraph(vertices) - val vars = getVarsFromExpr(graph, acc) + val vars = getVarsFromExpr(id_graph, acc) vars.foreach(v => { id_graph.addEdge(vertices(v), heap_vertex, new DefaultEdge) }) @@ -452,7 +447,7 @@ case class VarAnalysisGraph(prog: Program, case Apply(exp) => val id_graph = createIdentityGraph(vertices) - val vars = getVarsFromExpr(graph, exp) + val vars = getVarsFromExpr(id_graph, exp) vars.foreach(v => { id_graph.addEdge(vertices(v), heap_vertex, new DefaultEdge) }) @@ -460,32 +455,38 @@ case class VarAnalysisGraph(prog: Program, case Package(wand, _) => val id_graph = createIdentityGraph(vertices) - val vars = getVarsFromExpr(graph, wand) + val vars = getVarsFromExpr(id_graph, wand) vars.foreach(v => { id_graph.addEdge(vertices(v), heap_vertex, new DefaultEdge) }) id_graph case g@Goto(_) => - reportErrorWithMsg(ConsistencyError(s"${g} is an undefined statement for the universal introduction block", g.pos)) - graph + reportErrorWithMsg(ConsistencyError(s"${g} is an undefined statement for the modular information flow analysis", g.pos)) + createEmptyGraph(vertices) + + case OldCall(methodName,args,targets,_) => + val met = prog.findMethod(methodName) + val methodcall_graph: Graph[LocalVarDecl, DefaultEdge] = createEmptyGraph(vertices) + + createInfluencedByGraph(methodcall_graph, vertices, args, targets, met.formalArgs, met.formalReturns, met.posts) - case OldCall(_,_) => graph case s@_ => - reportErrorWithMsg(ConsistencyError(s"${s} is an undefined statement for the universal introduction block", s.pos)) - graph - //throw new UnsupportedOperationException("undefined statement for universal introduction block: " + s) + reportErrorWithMsg(ConsistencyError(s"${s} is an undefined statement for the modular information flow analysis", s.pos)) + createEmptyGraph(vertices) } } - def expInfluencesAll(exp:Exp, graph: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl,LocalVarDecl]) : Graph[LocalVarDecl, DefaultEdge] = { + + /** + * creates an edge between every variable in the expression to every variable that is in scope and returns resulting graph + */ + def expInfluencesAllVertices(exp:Exp, graph: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl,LocalVarDecl]) : Graph[LocalVarDecl, DefaultEdge] = { val id_graph = createIdentityGraph(vertices) - val inhale_vars = getVarsFromExpr(graph, exp) - inhale_vars.foreach(v => { - //val init_v = createInitialVertex(v) + val vars = getVarsFromExpr(graph, exp) + vars.foreach(v => { val init_v = vertices(v) - //id_graph.addEdge(init_v, heap_vertex, new DefaultEdge) vertices.keySet.foreach(k => { id_graph.addEdge(init_v, k, new DefaultEdge) }) @@ -494,9 +495,12 @@ case class VarAnalysisGraph(prog: Program, id_graph } + /** creates graph for methodcall and oldcall. maps expressions passed to methods to the method arguments, computes the graph based on the flow annotation, + * and finally maps the return variables to the variables that the method is assigned to. */ def createInfluencedByGraph(graph: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl,LocalVarDecl], arg_names: Seq[Exp], ret_names: Seq[LocalVar], method_arg_names: Seq[LocalVarDecl], method_ret_names: Seq[LocalVarDecl], posts: Seq[Exp]): Graph[LocalVarDecl, DefaultEdge] = { /** set of all target variables that have not been included in the influenced by expression up until now */ var retSet: Set[LocalVarDecl] = method_ret_names.toSet + heap_vertex + var methodcall_graph = copyGraph(graph) val method_arg_names_incl_heap = method_arg_names ++ Seq(heap_vertex) @@ -569,6 +573,8 @@ case class VarAnalysisGraph(prog: Program, methodcall_graph.addEdge(prov_decl, method_rets(return_decl), new DefaultEdge) }) + case _ => () + } /** now need to add to graph the edges from the method return variables to the target variables */ @@ -620,6 +626,12 @@ case class VarAnalysisGraph(prog: Program, }) } + /** remove rest of .ret variable incase no assigment */ + method_rets.values.foreach(ret_vert => { + methodcall_graph.removeVertex(ret_vert) + }) + + /** remove edge from the .arg_ to the .init vertex */ copy_arg_graph = copyGraph(methodcall_graph) @@ -641,19 +653,22 @@ case class VarAnalysisGraph(prog: Program, /** Since variables that are not assigned to should have an edge from their initial value to their 'end'-value */ val vert_wout_lhs = vertices.removedAll(targets_decl) - methodcall_graph = addMissingEdges(methodcall_graph, vert_wout_lhs) + methodcall_graph = addIdentityEdges(methodcall_graph, vert_wout_lhs) methodcall_graph } - def writtenTo(vertices: Map[LocalVarDecl,LocalVarDecl], stmt: Stmt): Option[Set[LocalVarDecl]] = { + /** + * get the variables that were modified by the statement stmt + */ + def getModifiedVars(vertices: Map[LocalVarDecl,LocalVarDecl], stmt: Stmt): Option[Set[LocalVarDecl]] = { var output: Option[Set[LocalVarDecl]] = None stmt match { case Seqn(ss, _) => for (s <- ss) { output match { - case None => output = writtenTo(vertices, s) - case Some(v) => output = Some(v ++ writtenTo(vertices, s).getOrElse(Set[LocalVarDecl]())) + case None => output = getModifiedVars(vertices, s) + case Some(v) => output = Some(v ++ getModifiedVars(vertices, s).getOrElse(Set[LocalVarDecl]())) } } @@ -670,8 +685,8 @@ case class VarAnalysisGraph(prog: Program, } res case If(_, thn, els) => - val writtenThn: Option[Set[LocalVarDecl]] = writtenTo(vertices, thn) - val writtenEls: Option[Set[LocalVarDecl]] = writtenTo(vertices, els) + val writtenThn: Option[Set[LocalVarDecl]] = getModifiedVars(vertices, thn) + val writtenEls: Option[Set[LocalVarDecl]] = getModifiedVars(vertices, els) (writtenThn, writtenEls) match { case (None, None) => None case (Some(_), None) => writtenThn @@ -680,7 +695,7 @@ case class VarAnalysisGraph(prog: Program, } case While(_, _, body) => - writtenTo(vertices, body) + getModifiedVars(vertices, body) case MethodCall(_, _, _) => None @@ -699,361 +714,4 @@ case class VarAnalysisGraph(prog: Program, None } } - - -} - - -/** - * ************************************ - * * - * old Graph version * - * * - * ************************************ - */ -/* -trait VarAnalysisGraph { - def reportErrorWithMsg(error: AbstractError): Unit - - /** - * - * @param graph to which vertices should be added - * @param scopedSeqnDeclarations variable declaration inside this codeblock - * @return graph with the newly added vertices - */ - def addNodes(graph: Graph[Declaration, DefaultEdge], scopedSeqnDeclarations: Seq[Declaration]): Graph[Declaration, DefaultEdge] = { - val result_graph: Graph[Declaration, DefaultEdge] = graph - scopedSeqnDeclarations.foreach(decl => result_graph.addVertex(decl)) - result_graph - } - - /** - * @param graph - * @return String that is the graph in DOT-language - * - */ - - def createDOT(graph:Graph[Declaration, DefaultEdge]) : String = { - val writer: StringWriter = new StringWriter() - writer.write("strict digraph G {\n") - graph.vertexSet().forEach(v => { - writer.write(" " + v.name + ";\n") - }) - graph.edgeSet().forEach(e => { - writer.write(" " + graph.getEdgeSource(e) + " -> " + graph.getEdgeTarget(e) + ";\n") - }) - writer.write("}\n") - writer.toString - } - - /** - * - * @param graph existing graph - * @param exp expressions from which all variables should be returned - * @return set of Variable declarations - */ - def getVarsFromExpr(graph: Graph[Declaration, DefaultEdge], exp: Exp): Set[Declaration] = { - val vars: Set[Declaration] = Set() - exp match { - case l@LocalVar(_, _) => { - var l_decl: Declaration = LocalVarDecl("", Int)() - graph.vertexSet().forEach(v => if (v.name == l.name) { l_decl = v }) - if (l_decl.name == "") { - l_decl = LocalVarDecl(l.name,l.typ)() - } - vars + l_decl - } - case BinExp(exp1, exp2) => { - getVarsFromExpr(graph, exp1) ++ getVarsFromExpr(graph, exp2) - } - case UnExp(exp) => { - getVarsFromExpr(graph, exp) - } - case _ => Set() - } - } - - /** - * - * @param graph graph that should be copied. Note: Shallow copy of graph instance, neither Vertices nor Edges are cloned - * @return copied graph - */ - def copyGraph(graph: Graph[Declaration, DefaultEdge]): Graph[Declaration, DefaultEdge] = { - val copied_graph = graph.asInstanceOf[AbstractBaseGraph[Declaration,DefaultEdge]].clone().asInstanceOf[DefaultDirectedGraph[Declaration, DefaultEdge]] - copied_graph - } - - /** - * - * @param graph - * @param src src vertex of edge (basically righthand-side of the assignment - * @param target target vertex of edge (basically lefthand-side of the assigment - * @return Graph with the new edge included - */ - def addTransitiveEdge(graph: Graph[Declaration, DefaultEdge], src: Declaration, target: Declaration): Graph[Declaration, DefaultEdge] = { - val new_graph = copyGraph(graph) - // the first condition depends on whether or not we want to include self loops - if (/*!src.equals(target) && */!new_graph.containsEdge(src, target)) { - new_graph.addEdge(src, target, new DefaultEdge) - /** go on level up and add those edges as well, this is enough - * since our invariant tells us that our graph is always transitively closed */ - new_graph.incomingEdgesOf(src).forEach(e => { - val src1 = new_graph.getEdgeSource(e) - /** edge only needs to be added if it doesn't exist yet */ - if (!new_graph.containsEdge(src1, target)) { - new_graph.addEdge(src1, target, new DefaultEdge) - } - }) - } - new_graph - } - - def compute_graph(graph: Graph[Declaration, DefaultEdge], stmt: Stmt): Graph[Declaration, DefaultEdge] = { - stmt match { - case Seqn(ss, scopedSeqnDeclarations) => { - val graph_copy: Graph[Declaration, DefaultEdge] = copyGraph(graph) - var new_graph = addNodes(graph_copy, scopedSeqnDeclarations) - for (s <- ss) { - val new_graph_copy: Graph[Declaration,DefaultEdge] = copyGraph(new_graph) - new_graph = compute_graph(new_graph_copy, s) - } - val final_graph: Graph[Declaration, DefaultEdge] = new DefaultDirectedGraph[Declaration, DefaultEdge](classOf[DefaultEdge]) - graph.vertexSet().forEach(v => final_graph.addVertex(v)) - new_graph.edgeSet().forEach(e => { - val source: Declaration = new_graph.getEdgeSource(e) - val target: Declaration = new_graph.getEdgeTarget(e) - if (final_graph.containsVertex(source) && final_graph.containsVertex(target)) { - final_graph.addEdge(source, target, e) - } - }) - final_graph - } - - case If(cond, thn, els) => { - val cond_variables = getVarsFromExpr(graph, cond) - val new_graph = copyGraph(graph) - - val new_graph_for_els: Graph[Declaration,DefaultEdge] = copyGraph(new_graph) - val thn_graph = compute_graph(new_graph, thn) - val els_graph = compute_graph(new_graph_for_els, els) - - //println("then graph: ", createDOT(thn_graph)) - //println("else graph: ", createDOT(els_graph)) - - - /** take union of these two graphs from the else and the then block - * First: make copy of thn_graph and declare this as the graph_union - * Second: add all edges from els_graph that are not in the graph_union yet */ - var graph_union: Graph[Declaration,DefaultEdge] = copyGraph(thn_graph) - els_graph.edgeSet().forEach(e => { - if (!graph_union.containsEdge(e)) { - graph_union.addEdge(els_graph.getEdgeSource(e), els_graph.getEdgeTarget(e), e) - } - }) - - cond_variables.foreach(src => { - val writtenToThn = writtenTo(new_graph, thn) - val writtenToEls = writtenTo(new_graph, els) - val allWrittenTo = writtenToThn.getOrElse(Set()) ++ writtenToEls.getOrElse(Set()) - allWrittenTo.foreach(t => { - /** otherwise it is an assigment to a variable that is only inside - * the scope of the block and therefore not relevant for us - */ - if (graph_union.containsVertex(t)) { - /** we need here transitive edge since otherwise the invariant may not hold (graph is transitively closed) */ - graph_union = addTransitiveEdge(graph_union, src, t) - // graph_union.addEdge(src, t, new DefaultEdge) - } - }) - }) - graph_union - } - - case w@While(cond, _, body) => { - val graph_copy : Graph[Declaration,DefaultEdge] = copyGraph(graph) - /** analyse one iteration of the while loop */ - val new_graph: Graph[Declaration, DefaultEdge] = compute_graph(graph_copy, If(cond,body,Seqn(Seq(), Seq())(body.pos))(body.pos)) - //println("graph") - //println(createDOT(graph)) - println("new_graph") - println(createDOT(new_graph)) - - /** check whether the edges are equal. - * First check whether both edge sets have the same size - * then go through each edge and check whether it also exists in the new graph*/ - var edges_equal: Boolean = graph.edgeSet().size().equals(new_graph.edgeSet().size()) - if (edges_equal) { - for (e1: DefaultEdge <- new_graph.edgeSet().asScala.toSet) { - edges_equal = false - for (e2: DefaultEdge <- graph.edgeSet().asScala.toSet) { - edges_equal = edges_equal || e1.equals(e2) - } - /** if no equal edge found then break out of the loop */ - if (!edges_equal) { - break() - } - } - } - if (new_graph.vertexSet().equals(graph.vertexSet()) && edges_equal) { - graph - } else { - compute_graph(new_graph, w) - } - } - - case LocalVarAssign(lhs, rhs) => { - val rhs_vars = getVarsFromExpr(graph, rhs) - //var lhs_decl: Declaration = LocalVarDecl("",Int)() - /** This way the position is the location of the assignment not the declaration. Better for error message but makes less sense I guess */ - val lhs_decl: Declaration = LocalVarDecl(lhs.name,lhs.typ)(lhs.pos) - //graph.vertexSet().forEach(v => if (v.name == lhs.name) {lhs_decl=v}) - //println("Before assignment: ", createDOT(graph)) - var new_graph: Graph[Declaration, DefaultEdge] = copyGraph(graph) - - val incomingEdges: Set[DefaultEdge] = graph.incomingEdgesOf(lhs_decl).asScala.toSet - - val edgesToRemove: Set[DefaultEdge] = graph.edgesOf(lhs_decl).asScala.toSet - - //println("Edges to remove", lhs, rhs, edgesToRemove) - /** remove all edges to and from the lhs in the new graph, since the lhs is reassigned */ - //println("before removing: New graph, ", createDOT(new_graph)) - edgesToRemove.foreach(e => new_graph.removeEdge(e)) - //println("after removing: New graph, ", createDOT(new_graph)) - - - /** add all new edges to the graph */ - rhs_vars.foreach(v => { - /** if v not equal to the lhs then add the Transitive edge to the new_graph */ - if(!v.equals(lhs_decl)) { - new_graph = addTransitiveEdge(new_graph, v, lhs_decl) - - /** if v is equal to the lhs then we need to add all the incoming edge to the lhs back into the new graph */ - } else { - incomingEdges.foreach(e => new_graph.addEdge(graph.getEdgeSource(e), graph.getEdgeTarget(e),e)) - } - //println("after adding the new edges: ",createDOT(new_graph)) - }) - //println("After assignment: ", createDOT(new_graph)) - - new_graph - } - - case i@Inhale(exp) => { - if (exp.isPure) { - graph - } else { - reportErrorWithMsg(ConsistencyError("There might be an impure inhale expression inside universal introduction block", i.pos)) - graph - } - } - - case Assume(_) => { - graph - } - - case Label(_, _) => { - graph - } - - /** TODO: Method call */ - case m@MethodCall(methodName, args, targets) => { - verifier.findMethod - reportErrorWithMsg(ConsistencyError("Might be not allowed method call inside universal introduction block", m.pos)) - /** maybe add to graph all edges from args to targets */ - /** somehow would have to check the influenced _ target _ by {...}. maybe like this?*/ - /* - for (s<-m.subnodes) { - if (s.isInstanceOf[PostconditionBlock]) { - - } - } - - */ - graph - } - - case f@FieldAssign(_, _) => { - reportErrorWithMsg(ConsistencyError("FieldAssign for universal introduction block", f.pos)) - graph - } - - case _ => { - throw new UnsupportedOperationException("undefined statement for universal introduction block") - } - - } - } - - def writtenTo(graph: Graph[Declaration, DefaultEdge], stmt: Stmt): Option[Set[Declaration]] = { - var output: Option[Set[Declaration]] = None - stmt match { - case Seqn(ss, _) => { - for (s <- ss) { - output match { - case None => output = writtenTo(graph,s) - case Some(v) => output = Some(v ++ writtenTo(graph,s).getOrElse(Set[Declaration]())) - } - - } - output - } - case LocalVarAssign(lhs, _) => { - // val lhs_var = LocalVarDecl(lhs.name, lhs.typ)(lhs.pos) - var lhs_decl: Declaration = LocalVarDecl("", Int)() - graph.vertexSet().forEach(v => { if (v.name == lhs.name) { lhs_decl = v } }) - Some(Set(lhs_decl)) - } - case If(_, thn, els) => { - val writtenThn: Option[Set[Declaration]] = writtenTo(graph, thn) - val writtenEls: Option[Set[Declaration]] = writtenTo(graph, els) - (writtenThn, writtenEls) match { - case (None, None) => None - case (Some(_), None) => writtenThn - case (None, Some(_)) => writtenEls - case (Some(t), Some(e)) => Some(t ++ e) - } - } - - case While(_, _, body) => { - writtenTo(graph, body) - } - - /** TODO */ - case MethodCall(_, _, _) => { - None - } - - - case Inhale(_) => { - None - } - - case Assume(_) => { - None - } - - case Label(_, _) => { - None - } - - case _ => { - None - } - } - } - /** Own Edge class such that we can define the equals method. - * Two edges should be equal if their source and target vertices are equal. */ - class DefaultEdge extends DefaultEdge { - override def equals(any: Any): Boolean = { - if(any.isInstanceOf[DefaultEdge]) { - val other = any.asInstanceOf[DefaultEdge] - this.getSource.equals(other.getSource) && this.getTarget.equals(other.getTarget) - } else { - false - } - } - } - -} - - */ \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala index 6cba38796..b357434d1 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala @@ -1,6 +1,6 @@ package viper.silver.plugin.standard.reasoning.analysis -import viper.silver.ast.{Assume, BinExp, Declaration, Exp, FieldAssign, If, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Seqn, Stmt, UnExp, While} +import viper.silver.ast.{Assume, BinExp, Declaration, Exp, FieldAssign, If, Inhale, Label, Literal, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Seqn, Stmt, UnExp, While} import viper.silver.plugin.standard.reasoning.UniversalIntro import viper.silver.verifier.{AbstractError, ConsistencyError} @@ -25,7 +25,7 @@ trait VarAnalysisSet { val tainted_vars: Set[Declaration] = all_tainted.intersect(vars_outside_blk) val problem_vars: String = tainted_vars.mkString(", ") val problem_pos: String = tainted_vars.map(vs => vs.pos).mkString(", ") - reportError(ConsistencyError("Universal introduction variable might have been assigned to variable " + problem_vars + " at positions (" + problem_pos + "), defined outside of the block", u.pos)) + reportError(ConsistencyError("Universal introduction variable might have been assigned to variable " + problem_vars/* + " at positions (" + problem_pos + ")*/+ ", defined outside of the block", u.pos)) } } @@ -40,7 +40,7 @@ trait VarAnalysisSet { stmt match { case Seqn(ss, decls) => { for (s <- ss) { - output = output ++ get_tainted_vars_stmt(output, s) + output = get_tainted_vars_stmt(output, s) } output } @@ -48,7 +48,7 @@ trait VarAnalysisSet { if (is_expr_tainted(tainted, rhs)) { tainted ++ Set(LocalVarDecl(lhs.name, lhs.typ)(stmt.pos)) } else { - tainted + tainted -- Set(LocalVarDecl(lhs.name, lhs.typ)(stmt.pos)) } case If(cond, thn, els) => { @@ -62,13 +62,6 @@ trait VarAnalysisSet { } case w@While(cond, _, body) => { - /* - if (is_expr_tainted(tainted, cond)) { - writtenTo(body).getOrElse(Set()) ++ Set[Declaration]() - } else { - get_tainted_vars_stmt(tainted, body) - } - */ val new_tainted = get_tainted_vars_stmt(tainted, If(cond,body,Seqn(Seq(), Seq())(body.pos))(body.pos)) if (new_tainted.equals(tainted)) { tainted @@ -78,9 +71,8 @@ trait VarAnalysisSet { } case m@MethodCall(_, _, _) => { - //Problem cannot reportError inside this trait! reportErrorWithMsg(ConsistencyError("Might be not allowed method call inside universal introduction block", m.pos)) - tainted // TODO: what needs to be returned here? + tainted } case i@Inhale(exp) => { @@ -88,7 +80,7 @@ trait VarAnalysisSet { tainted } else { reportErrorWithMsg(ConsistencyError("There might be an impure inhale expression inside universal introduction block", i.pos)) - tainted //What needs to be returned here + tainted } } @@ -102,76 +94,17 @@ trait VarAnalysisSet { /** TODO: Do not allow Heap assignments */ case f@FieldAssign(lhs, rhs) => { - reportErrorWithMsg(ConsistencyError("FieldAssign for universal introduction block", f.pos)) - tainted // TODO: what needs to be returned here ? - } - - case _ => { - throw new UnsupportedOperationException("undefined statement for universal introduction block") - } - } - } - - /* - def checkStmt(tainted: mutable.Set[Declaration], stmt: Stmt, allTainted: Boolean) : mutable.Set[Declaration] = { - stmt match { - case Seqn(ss, _) => { - for (s <- ss) { - tainted ++= checkStmt(tainted, s, allTainted) - } - tainted - } - case LocalVarAssign(lhs, rhs) => { - if (allTainted || checkExpr(tainted, rhs)) { - tainted += LocalVarDecl(lhs.name, lhs.typ)(stmt.pos) - } - tainted - } - case If(cond, thn, els) => { - if(checkExpr(tainted,cond)){ - val new_tainted: mutable.Set[Declaration] = checkStmt(tainted, thn, true) - tainted ++= checkStmt(new_tainted, els,true) - } else { - val new_tainted: mutable.Set[Declaration] = checkStmt(tainted, thn, allTainted) - tainted ++= checkStmt(new_tainted, els, allTainted) - } - tainted - } - - case While(cond, _, body) => { - if (checkExpr(tainted, cond)) { - tainted ++= checkStmt(tainted, body, true) - } else { - tainted ++= checkStmt(tainted, body, allTainted) - } - tainted - } - - case MethodCall(_, _, _) => { - //Problem cannot reportError inside this trait! - throw new IllegalArgumentException("Method call universal introduction") - } - - case Inhale(exp) => { - tainted ++= addExprToTainted(tainted, exp) + reportErrorWithMsg(ConsistencyError("FieldAssign for modular flow analysis with sets", f.pos)) tainted } - case Assume(_) => { - tainted - } - - case Label(_, _) => { + case s@_ => { + reportErrorWithMsg(ConsistencyError("undefined statement for modular flow analysis with set", s.pos)) tainted } - - case _ => { - throw new UnsupportedOperationException("undefined statement for universal introduction block") - } } } - */ /** * expressions that should be added to tainted (e.g. for instance for inhale statements @@ -279,157 +212,4 @@ trait VarAnalysisSet { } } } - - /* - - /** - * check that universal introduction variables aren't changed. - * True if immutable variables are reassigned false otherwise - */ - - // change this so it is called written (or writtenTo) - // writtenTo(stmt:Stmt) : optionalSet[LocalVarDecl] {} - def check_is_reassigned(vars: Seq[LocalVarDecl], stmt: Stmt): Seq[LocalVarDecl] = { - var output = Seq[LocalVarDecl]() - stmt match { - case Seqn(ss, _) => { - for (s <- ss) { - output = output ++ check_is_reassigned(vars, s) - } - output - } - case LocalVarAssign(lhs, _) => { - var output = Seq[LocalVarDecl]() - val lhs_var = LocalVarDecl(lhs.name, lhs.typ)(lhs.pos) - if (vars.contains(lhs_var)) { - output ++= Seq(lhs_var) - } - output - } - case If(_, thn, els) => { - check_is_reassigned(vars, thn) ++ check_is_reassigned(vars, els) - } - - case While(_, _, body) => { - check_is_reassigned(vars, body) - } - - /** return false for now if immutable vars are part of arguments of method call */ - case MethodCall(_, args, _) => { - var output = Seq[LocalVarDecl]() - for (exp <- args) { - output = output ++ expr_contains_immutable(vars, exp) - } - output - } - - - case Inhale(_) => { - Seq[LocalVarDecl]() - } - - case Assume(_) => { - Seq[LocalVarDecl]() - } - - case Label(_, _) => { - Seq[LocalVarDecl]() - } - - case _ => { - Seq[LocalVarDecl]() - } - } - } - - def expr_contains_immutable(vars: Seq[LocalVarDecl], exp: Exp): Seq[LocalVarDecl] = { - exp match { - case l@LocalVar(_, _) => { - val v = LocalVarDecl(l.name, l.typ)(l.pos) - if(vars.contains(v)){ - Seq(v) - } else { - Seq() - } - } - case BinExp(exp1, exp2) => { - expr_contains_immutable(vars, exp1) ++ expr_contains_immutable(vars, exp2) - } - case UnExp(exp) => { - expr_contains_immutable(vars, exp) - } - case _ => Seq() - } - } - - */ - - /* - - def check_is_reassigned_bool(vars: Seq[LocalVarDecl], stmt: Stmt) : Boolean = { - var output = false - stmt match { - case Seqn(ss, _) => { - for (s <- ss) { - output = check_is_reassigned(vars, s) - } - output - } - case LocalVarAssign(lhs, _) => { - var output: Boolean = false - if (vars.contains(LocalVarDecl(lhs.name, lhs.typ)(lhs.pos))) { - output = true - } - output - } - case If(_, thn, els) => { - check_is_reassigned(vars, thn) || check_is_reassigned(vars, els) - } - - case While(_, _, body) => { - check_is_reassigned(vars, body) - } - - /** return false for now if immutable vars are part of arguments of method call */ - case MethodCall(_, args, _) => { - var output: Boolean = false - for (exp <- args) { - output = output || expr_contains_immutable(vars, exp) - } - output - } - - - case Inhale(_) => { - false - } - - case Assume(_) => { - false - } - - case Label(_, _) => { - false - } - - case _ => { - false - } - } - } - - def expr_contains_immutable_bool(vars: Seq[LocalVarDecl], exp: Exp): Boolean = { - exp match { - case l@LocalVar(_, _) => { - vars.contains(LocalVarDecl(l.name, l.typ)(l.pos)) - } - case BinExp(exp1, exp2) => { - expr_contains_immutable(vars, exp1) || expr_contains_immutable(vars, exp2) - } - case UnExp(exp) => { - expr_contains_immutable(vars, exp) - } - case _ => false - } - }*/ } diff --git a/src/test/resources/reasoning/existential_elim.vpr b/src/test/resources/reasoning/existential_elim.vpr new file mode 100644 index 000000000..0b849bb06 --- /dev/null +++ b/src/test/resources/reasoning/existential_elim.vpr @@ -0,0 +1,31 @@ +function eq(x: Int, y: Int): Bool { + x == y +} + +method ex1() + requires exists x: Int :: { eq(x, 42) } eq(x, 42) +{ + obtain x:Int where eq(x, 42) + assert x == 42 +} + + +field f: Int + +method ex2() +{ + //:: ExpectedOutput(consistency.error) + obtain x: Ref where {x.f} acc(x.f) +} + +function geq(x:Int, y:Int) : Bool +{ + x>=y +} + +method ex3() +{ + assert geq(3, 0) + obtain x:Int, y:Int where {geq(x,y)} geq(x,y) + assert x>=y +} diff --git a/src/test/resources/reasoning/existential_elim_impure.vpr b/src/test/resources/reasoning/existential_elim_impure.vpr deleted file mode 100644 index b88d51ab9..000000000 --- a/src/test/resources/reasoning/existential_elim_impure.vpr +++ /dev/null @@ -1,9 +0,0 @@ - -field f: Int - -method ex2() -{ - //:: ExpectedOutput(consistency.error) - obtain x: Ref where {x.f} acc(x.f) - //assert exists x: Ref :: acc(x.f) -} \ No newline at end of file diff --git a/src/test/resources/reasoning/existential_elim_simple.vpr b/src/test/resources/reasoning/existential_elim_simple.vpr deleted file mode 100644 index fbd5c3bfa..000000000 --- a/src/test/resources/reasoning/existential_elim_simple.vpr +++ /dev/null @@ -1,10 +0,0 @@ -function eq(x: Int, y: Int): Bool { - x == y -} - -method ex1() - requires exists x: Int :: { eq(x, 42) } eq(x, 42) -{ - obtain x:Int where eq(x, 42) - assert x == 42 -} diff --git a/src/test/resources/reasoning/existential_elim_trigger.vpr b/src/test/resources/reasoning/existential_elim_trigger.vpr deleted file mode 100644 index 980e43b76..000000000 --- a/src/test/resources/reasoning/existential_elim_trigger.vpr +++ /dev/null @@ -1,11 +0,0 @@ -function geq(x:Int, y:Int) : Bool -{ - x>=y -} - -method ex1() -{ - assert geq(3, 0) - obtain x:Int, y:Int where {geq(x,y)} geq(x,y) - assert x>=y -} diff --git a/src/test/resources/reasoning/immutableVar.vpr b/src/test/resources/reasoning/immutableVar.vpr index c7805ab1e..3d5b5f6dd 100644 --- a/src/test/resources/reasoning/immutableVar.vpr +++ b/src/test/resources/reasoning/immutableVar.vpr @@ -12,7 +12,7 @@ method xassigned() //:: ExpectedOutput(consistency.error) prove forall x:Int {P(x)} assuming P(x) implies Q(x) { var y:Int := x+1 - x:=2 // x should be immutable + x:=2 } } @@ -39,21 +39,6 @@ method xif() } } -method dosomething(x: Int) -{ - var y:Int := x -} - -method xmcall() -{ - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - // could influence the heap - - dosomething(x) - } -} - method xmultOK() { prove forall x:Int, y:Int {P(x),P(y)} assuming P(x) implies Q(x) { diff --git a/src/test/resources/reasoning/influenced_heap.vpr b/src/test/resources/reasoning/influenced_heap.vpr new file mode 100644 index 000000000..bf120b2a4 --- /dev/null +++ b/src/test/resources/reasoning/influenced_heap.vpr @@ -0,0 +1,20 @@ +field f:Int + + +method exampleHeapArg(b:Ref) returns (c:Int) +requires acc(b.f) +influenced heap by {heap,b} +//:: ExpectedOutput(consistency.error) +influenced c by { b } +{ + + c := b.f +} + + +method exampleHeap(b:Int) returns (c:Int) +influenced heap by {heap} +influenced c by {} +{ + c := 3 +} \ No newline at end of file diff --git a/src/test/resources/reasoning/set_vs_graph.vpr b/src/test/resources/reasoning/set_vs_graph.vpr new file mode 100644 index 000000000..1b1bd6bcc --- /dev/null +++ b/src/test/resources/reasoning/set_vs_graph.vpr @@ -0,0 +1,170 @@ +function P(x: Int) : Bool { + x == 0 +} + +function Q(x: Int) : Bool { + x == 0 +} + +method simple(x: Int) returns (z:Int) +{ + z := x +} + + +method mIfOK1(x:Int) returns (w:Int) +{ + w := 0 + var l :Int := 0 + var g: Int := 0 + if (l >= -1) { + g := x + } else { + w := 4 + } +} + + + +method mWhileOK(x:Int) returns (z:Int) +{ + var y: Int := 0 + while(y<100) { // will only be tainted after the 5th? iteration + var x4: Int := 0 + var x3: Int := 0 + var x2: Int := 0 + var x1: Int := 0 + z := x4 + x4 := x3 + x3 := x2 + x2 := x1 + x1 := x + y := y+1 + } +} + + +method mWhileNOK(x:Int) returns (z:Int) +{ + var y: Int := 0 + var x2: Int := 0 + var x1: Int := 0 + + while(y<100) { + z := x2 + x2 := x1 + x1 := x + y := y+1 + } +} + + +//graph at the end such that z is influenced by .init_z and .init_x +//correct because if loop not executed then z is influenced by .init_z? +method mWhileNOK2(x:Int) returns (z:Int) +{ + var y: Int := x + var x2: Int := 0 + var x1: Int := 0 + + while(y<100) { + z := x2 + x2 := x1 + y := y+1 + } +} + +method m0(x: Int) returns (z:Int) +{ + var y: Int := x+1 + if (true) { var w:Int } // this should work -> two separate blocks + // var y:Int := 0 //here duplicate identifier +} +method mIndirect(x:Int) returns (z:Int) +{ + var y:Int := x+1 + z := y + // problem if here var y:Int := 0 -> this will also be in tainted set + if (true) { var w:Int } +} + +method mIfCnd(x: Int) returns (z:Int, y:Int) +{ + if(x>5) { + z := 3 + } else { + y := 5 + } +} + + +method mIfOK2(x:Int) returns (w:Int) +{ + var l :Int := 0 + if (l >= -1) { + l := x + } else { + w := 4 // should be SAFE + } +} + +method mAssignOK2(x:Int) returns (r:Int) +{ + r:=x + r:=1 + +} + +method mAssignOK3(x:Int) returns (r:Int) +{ + r:=x + var y: Int + r:= y +} + +method m(a:Int,c:Bool) returns (d:Int,e:Int) +{ + while(c) { + d:=e + e:=a + } +} + +method mMany(a:Int,b:Int,c:Int,d:Int,e:Int,f:Int,g:Int,h:Int,i:Int,j:Int,k:Int,l:Int,m1:Int,n:Int,o:Int,p:Int,q:Int,r:Int,s:Int) returns (r1:Int,r2:Int,r3:Int,r4:Int,r5:Int,r6:Int) +requires p!=0 +{ + if(a 0 +ensures y + w + x > 7 +ensures y == 3 +{ + y := 3 + w := 4 +} + +method mOldCallOK1() +{ + var x: Int := 5 + label l + var z: Int + var a: Int + z,a := oldCall[l](callLemma(x + 5)) +} + + +method exProjDesc() +{ + var k: Int + //:: ExpectedOutput(consistency.error) + prove forall x: Int assuming true implies x==k { + k := x + } +} + + +method mWhileCheck() +{ + var c:Bool + var e:Int + var d:Int + //:: ExpectedOutput(consistency.error) + prove forall a:Int {P(a)} assuming P(a) implies Q(a) { + while (c) + { + d:=e + e:=a + } + } +} + + +method m(a:Int, b:Int) +returns (d:Int) +influenced d by {a} +influenced heap by {heap} +{ + d:=a +} + +method m1() +{ + var b:Int + var d:Int + //:: ExpectedOutput(consistency.error) + prove forall a:Int {P(a)} assuming P(a) implies Q(a) { + d:=m(a,b) + } +} + +method mInhaleAccess() +{ + var d:Ref + //:: ExpectedOutput(consistency.error) + prove forall a:Int {P(a)} assuming P(a) implies Q(a) { + inhale acc(d.f) + d.f:=a + } +} + + +method mLiteralAssign() +{ + var d: Int + prove forall x:Int {P(x)} assuming P(x) implies Q(x) + { + d:=x + d:=3 + } +} + +method mUnivIntro() +{ + var d: Int + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) + { + prove forall y:Int {P(y)} assuming P(y) implies Q(y) + { + d:=x + } + } +} + +method dosomething(x: Int) +{ + var y:Int := x +} + +method xmcall() +{ + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + dosomething(x) + } +} diff --git a/src/test/resources/reasoning/test2.vpr b/src/test/resources/reasoning/test2.vpr new file mode 100644 index 000000000..b4813bfd2 --- /dev/null +++ b/src/test/resources/reasoning/test2.vpr @@ -0,0 +1,127 @@ +field f: Int + +// Should be accepted +method test1(x: Ref) returns (z: Ref) + influenced z by {x} +{ + z := x +} + +// Should be rejected +method test2(x: Ref) returns (z: Ref) +//:: ExpectedOutput(consistency.error) + influenced z by {heap} +{ + z := x +} + +// Should be accepted +method test3(x: Perm) returns (y: Perm) + influenced y by {x} +{ + y := x +} + +// Should be rejected +method test4(x: Perm) returns (y: Perm) +//:: ExpectedOutput(consistency.error) + influenced y by {heap} +{ + y := x +} + +// Should be accepted (we interpret absence of influenced by as influenced by everything, as shown below) +method test5(x: Int, y: Int) returns (a: Int, b: Int) +{ + a := x + y + b := x + y +} + +method test5_equivalent(x: Int, y: Int) returns (a: Int, b: Int) + influenced a by {y, x, heap} + influenced b by {x, heap, y} +{ + a := x + y + b := x + y +} + +// The annotation "influenced heap by {heap}" should be rejected, +// since test5 has no flow-annotation for the heap, thus it should be +// considered as influenced heap by {heap, x, y} +method test5_caller(x: Int, y: Int) returns (a: Int, b: Int) +//:: ExpectedOutput(consistency.error) + influenced heap by {heap} + influenced a by {y, x, heap} + influenced b by {x, heap, y} +{ + a, b := test5(x, y) +} + +// should be rejected (at most 1 line per return variable) +method test6(x: Int, y: Int) returns (a: Int, b: Int) + influenced a by {y, x} + influenced b by { } +//:: ExpectedOutput(consistency.error) + influenced b by {x, y} +{ + a := x + y + b := x + y +} + +// should be rejected: x cannot be influenced by anything, since it's a formal argument +method test7(x: Int, y: Int) returns (a: Int, b: Int) + influenced a by {y, x} + influenced b by {x, y} +//:: ExpectedOutput(consistency.error) + influenced x by {x} +{ + a := x + y + b := x + y +} + +// The most precise annotation for this test is +// influenced a by {x, y} +// influenced b by {x, y} +// influenced heap by {heap, x} +method caller_test6(x: Int, y: Int, r: Ref) returns (a: Int, b: Int) + requires acc(r.f) + influenced b by {x, y} + influenced a by {x, y} +{ + a, b := test6(x, y) + r.f := x +} + +// Should be rejected: the heap (r.f in this case) +// is influenced by x in caller_test6 +method caller_caller_test6(x: Int, y: Int, r: Ref) + requires acc(r.f) +//:: ExpectedOutput(consistency.error) + influenced heap by {heap} +{ + var a: Int + var b: Int + a, b := caller_test6(x, y, r) +} + +// Should be rejected: The heap (x.f in this case) +// is influenced by y +method test8(x: Ref, y: Int) + requires acc(x.f) +//:: ExpectedOutput(consistency.error) + influenced heap by {heap} +{ + x.f := y +} + +// Should be rejected: The heap (permission to x.f in this case) +// is influenced by y +method test9(x: Ref, y: Int) + requires acc(x.f) +//:: ExpectedOutput(consistency.error) + influenced heap by {heap} +{ + if (y == 0) { + exhale acc(x.f) + } +} \ No newline at end of file diff --git a/src/test/resources/reasoning/outsideBlockAssignmentUnivIntro.vpr b/src/test/resources/reasoning/test3.vpr similarity index 100% rename from src/test/resources/reasoning/outsideBlockAssignmentUnivIntro.vpr rename to src/test/resources/reasoning/test3.vpr diff --git a/src/test/resources/reasoning/universal_intro_simple.vpr b/src/test/resources/reasoning/universal_intro.vpr similarity index 70% rename from src/test/resources/reasoning/universal_intro_simple.vpr rename to src/test/resources/reasoning/universal_intro.vpr index 5d1235c83..2835d23ef 100644 --- a/src/test/resources/reasoning/universal_intro_simple.vpr +++ b/src/test/resources/reasoning/universal_intro.vpr @@ -28,3 +28,23 @@ method ex2() } assert greater(i,j) } + + +function P(k: Int) : Bool +{ + false +} + +function Q(k: Int) : Bool +{ + k==2 +} + +//assuming false +method m1() +{ + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + var y:Int := x+1 + } +} + diff --git a/src/test/resources/reasoning/universal_intro_assuming_false.vpr b/src/test/resources/reasoning/universal_intro_assuming_false.vpr deleted file mode 100644 index e4596d534..000000000 --- a/src/test/resources/reasoning/universal_intro_assuming_false.vpr +++ /dev/null @@ -1,19 +0,0 @@ -function P(k: Int) : Bool -{ - false -} - -function Q(k: Int) : Bool -{ - k==2 -} - -method m1() -{ - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - var y:Int := x+1 - } -} - - - From 663797ff26178200027219fd4a8c1af749cd9a58 Mon Sep 17 00:00:00 2001 From: Linard Arquint Date: Tue, 27 Feb 2024 17:38:29 +0100 Subject: [PATCH 08/41] fixes parser to correctly parse syntax of reasoning plugin --- .../viper/silver/frontend/SilFrontend.scala | 4 +- .../reasoning/ReasoningASTExtension.scala | 24 +++- .../reasoning/ReasoningPASTExtension.scala | 46 +++++- .../standard/reasoning/ReasoningPlugin.scala | 37 ++++- .../analysis/SetGraphComparison.scala | 4 +- .../reasoning/analysis/VarAnalysisGraph.scala | 135 +++++++----------- .../reasoning/analysis/VarAnalysisSet.scala | 115 +++++---------- .../scala/viper/silver/reporter/Message.scala | 7 +- .../viper/silver/reporter/Reporter.scala | 23 ++- .../resources/reasoning/existential_elim.vpr | 2 +- src/test/resources/reasoning/immutableVar.vpr | 2 +- src/test/resources/reasoning/old_call.vpr | 12 ++ 12 files changed, 234 insertions(+), 177 deletions(-) create mode 100644 src/test/resources/reasoning/old_call.vpr diff --git a/src/main/scala/viper/silver/frontend/SilFrontend.scala b/src/main/scala/viper/silver/frontend/SilFrontend.scala index fa720e914..82cb8ec2b 100644 --- a/src/main/scala/viper/silver/frontend/SilFrontend.scala +++ b/src/main/scala/viper/silver/frontend/SilFrontend.scala @@ -261,7 +261,9 @@ trait SilFrontend extends DefaultFrontend { if (state == DefaultStates.ConsistencyCheck && _errors.isEmpty) { filter(_program.get) match { - case Succ(program) => _program = Some(program) + case Succ(program) => + _program = Some(program) + reporter report PluginTransformationsAppliedMessage(program) case Fail(errors) => _errors ++= errors } } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala index 189213f58..2f73d1061 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala @@ -141,4 +141,26 @@ case class OldCall(methodName: String, args: Seq[Exp], rets: Seq[LocalVar], l:La } override val extensionSubnodes: Seq[Node] = args ++ rets -} \ No newline at end of file +} +/* +case class OldCall(methodName: String, args: Seq[Exp], l:Label)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionExp { + override lazy val check: Seq[ConsistencyError] = { + var s = Seq.empty[ConsistencyError] + if (!Consistency.noResult(this)) + s :+= ConsistencyError("Result variables are only allowed in postconditions of functions.", pos) + s ++= args.flatMap(Consistency.checkPure) + s + } + + override lazy val prettyPrint: PrettyPrintPrimitives#Cont = + text("oldCall") <> brackets(show(l)) <> text(methodName) <> nest(defaultIndent, parens(ssep(args map show, group(char(',') <> line)))) + + override val extensionSubnodes: Seq[Node] = args + + override def extensionIsPure: Boolean = true + + override def typ: Type = ??? + + override def verifyExtExp(): VerificationResult = ??? +} + */ \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala index 617e18744..ffb029691 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -2,7 +2,7 @@ package viper.silver.plugin.standard.reasoning import viper.silver.ast.{ExtensionExp, Label, LocalVar, LocalVarDecl, Position, Seqn, Stmt, Trigger} import viper.silver.parser.TypeHelper.Bool -import viper.silver.parser.{NameAnalyser, PCall, PCallable, PDelimited, PExp, PExtender, PGrouped, PIdnRef, PIdnUseExp, PKeywordLang, PKeywordStmt, PKw, PLabel, PLocalVarDecl, PNode, PReserved, PSeqn, PStmt, PSym, PSymOp, PTrigger, PType, PTypeSubstitution, Translator, TypeChecker} +import viper.silver.parser.{NameAnalyser, PAssignTarget, PCall, PCallable, PDelimited, PExp, PExtender, PGrouped, PIdnRef, PKeywordLang, PKeywordStmt, PKw, PLabel, PLocalVarDecl, PNode, PReserved, PSeqn, PStmt, PSym, PSymOp, PTrigger, PType, PTypeSubstitution, Translator, TypeChecker} case object PObtainKeyword extends PKw("obtain") with PKeywordLang with PKeywordStmt case object PWhereKeyword extends PKw("where") with PKeywordLang @@ -118,8 +118,8 @@ case class PLemmaClause()(val pos: (Position,Position)) extends PExtender with P } } -case class POldCall(lhs: Option[(PDelimited[PIdnUseExp, PSym.Comma], PSymOp.Assign)], oldCallKw: PReserved[POldCallKeyword.type], lbl: PGrouped[PSym.Bracket, Either[PKw.Lhs, PIdnRef[PLabel]]], call: PGrouped[PSym.Paren, PCall])(val pos: (Position, Position)) extends PExtender with PStmt { - lazy val targets: Seq[PIdnUseExp] = lhs.map(_._1.toSeq).getOrElse(Seq.empty) +case class POldCall(lhs: PDelimited[PExp with PAssignTarget, PSym.Comma], op: Option[PSymOp.Assign], oldCallKw: PReserved[POldCallKeyword.type], lbl: PGrouped[PSym.Bracket, Either[PKw.Lhs, PIdnRef[PLabel]]], call: PGrouped[PSym.Paren, PCall])(val pos: (Position, Position)) extends PExtender with PStmt { + lazy val targets: Seq[PExp with PAssignTarget] = lhs.toSeq lazy val idnref: PIdnRef[PCallable] = call.inner.idnref lazy val args: Seq[PExp] = call.inner.args @@ -137,3 +137,43 @@ case class POldCall(lhs: Option[(PDelimited[PIdnUseExp, PSym.Comma], PSymOp.Assi OldCall(idnref.name, args map t.exp, (targets map t.exp).asInstanceOf[Seq[LocalVar]], Label(labelName, Seq())(t.liftPos(lbl)))(t.liftPos(this)) } } + +/** + * oldCall that does not assign any return parameters. + * Note that this node is only used for parsing and is translated to `POldCall` before typechecking + */ +case class POldCallStmt(oldCallKw: PReserved[POldCallKeyword.type], lbl: PGrouped[PSym.Bracket, Either[PKw.Lhs, PIdnRef[PLabel]]], funcApp: PGrouped[PSym.Paren, PCall])(val pos: (Position, Position)) extends PExtender with PStmt { + lazy val call: PCall = funcApp.inner + lazy val idnref: PIdnRef[PCallable] = call.idnref + lazy val args: Seq[PExp] = call.args + + // POldCallStmt are always transformed by `beforeResolve` in `ReasoningPlugin`. Thus, calling `typecheck` indicates a logical error + override def typecheck(t: TypeChecker, n: NameAnalyser): Option[Seq[String]] = throw new AssertionError(s"POldCallStmt '$this' should have been transformed before typechecking") + + // we do not translate this expression but `POldCall` which is created before resolution + override def translateStmt(t: Translator): Stmt = throw new AssertionError(s"POldCallStmt '$this' should have been transformed before typechecking") +} + +/** + * Note that this node is only used for parsing and is translated to `POldCall` before typechecking + */ +case class POldCallExp(oldCallKw: PReserved[POldCallKeyword.type], lbl: PGrouped[PSym.Bracket, Either[PKw.Lhs, PIdnRef[PLabel]]], call: PGrouped[PSym.Paren, PCall])(val pos: (Position, Position)) extends PExtender with PExp { + lazy val idnref: PIdnRef[PCallable] = call.inner.idnref + lazy val args: Seq[PExp] = call.inner.args + + override def typeSubstitutions: Seq[PTypeSubstitution] = Seq(PTypeSubstitution.id) + + override def forceSubstitution(ts: PTypeSubstitution): Unit = {} + + override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = typecheck(t, n) + + override def typecheck(t: TypeChecker, n: NameAnalyser): Option[Seq[String]] = { + // this node should get translated to `POldCall` but `beforeResolve` in `ReasoningPlugin` performs this translation + // only if its parent node is a PAssign. Thus, an invocation of this function indicates that this expression occurs + // at an unsupported location within the AST. + Some(Seq(s"oldCalls are only supported as statements or as the right-hand side of an assignment")) + } + + // we do not translate this expression but `POldCall` which is created before resolution + override def translateExp(t: Translator): ExtensionExp = throw new AssertionError(s"POldCallExp '$this' should have been transformed before typechecking") +} diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index cda2120ba..25a2424ff 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -5,7 +5,7 @@ import fastparse._ import org.jgrapht.Graph import org.jgrapht.graph.{DefaultDirectedGraph, DefaultEdge} import viper.silver.ast._ -import viper.silver.ast.utility.rewriter.Traverse +import viper.silver.ast.utility.rewriter.{StrategyBuilder, Traverse} import viper.silver.ast.utility.ViperStrategy import viper.silver.parser.FastParserCompanion.whitespace import viper.silver.parser._ @@ -59,12 +59,25 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, // lemma clause is completely artificial and is created out of nowhere at the parser's current position def lemmaClause[$: P]: P[PLemmaClause] = (Pass(()) map { _ => PLemmaClause()(_) }).pos - /** parser for oldCall statement */ + /** parsers for oldCall statement */ + /* + Note that the following definition of old call, i.e., `a, b := oldCall[L](lemma())` causes issues with backtracking + because depending on whether `oldCall` is added at the beginning and end of the list of statement parsers, the parser + has to backtrack to parse assignments and old calls, resp. def oldCall[$: P]: P[POldCall] = - P(((fp.idnuse.delimited(PSym.Comma) ~ PSymOp.Assign).? ~ P(POldCallKeyword) ~ fp.oldLabel.brackets ~ fp.funcApp.parens) map { + P(((fp.idnuse.delimited(PSym.Comma) ~ P(PSymOp.Assign)).? ~ P(POldCallKeyword) ~ fp.oldLabel.brackets ~ fp.funcApp.parens) map { case (lhs, oldCallKw, lbl, call) => POldCall(lhs, oldCallKw, lbl, call)(_) + }).pos + */ + def oldCallStmt[$: P]: P[POldCallStmt] = + P((P(POldCallKeyword) ~/ fp.oldLabel.brackets ~/ fp.funcApp.parens) map { + case (oldCallKw, lbl, funcApp) => POldCallStmt(oldCallKw, lbl, funcApp)(_) }).pos + def oldCallExp[$: P]: P[POldCallExp] = + P((P(POldCallKeyword) ~ fp.oldLabel.brackets ~ fp.funcApp.parens) map { + case (oldCallKw, lbl, call) => POldCallExp(oldCallKw, lbl, call)(_) + }).pos /** Add existential elimination and universal introduction to the parser. */ override def beforeParse(input: String, isImported: Boolean): String = { @@ -90,12 +103,28 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, ParserExtension.addNewPostCondition(lemma(_)) /** add the oldCall as a new stmt */ - ParserExtension.addNewStmtAtStart(oldCall(_)) + ParserExtension.addNewStmtAtEnd(oldCallStmt(_)) + ParserExtension.addNewExpAtEnd(oldCallExp(_)) input } + override def beforeResolve(input: PProgram): PProgram = { + // we change `oldCallExp` and `oldCallStmt` (which made parsing easier) to `oldCall`, which makes the translation easier + def transformStrategy[T <: PNode](input: T): T = StrategyBuilder.Slim[PNode]({ + case a@PAssign(delimitedTargets, op, c: POldCallExp) => POldCall(delimitedTargets, op, c.oldCallKw, c.lbl, c.call)(a.pos) + case o@POldCallStmt(oldCallKw, lbl, call) => POldCall(PDelimited(None, Nil, None)(oldCallKw.pos), None, oldCallKw, lbl, call)(o.pos) + }).recurseFunc({ + // only visit statements + case _: PExp => Seq() + case n: PNode => n.children collect { case ar: AnyRef => ar } + }).execute(input) + + transformStrategy(input) + } + + override def beforeVerify(input: Program): Program = { /** for evaluation purposes */ diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala index 28e2b5368..63567c984 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala @@ -14,7 +14,7 @@ trait SetGraphComparison extends VarAnalysisSet { def computeGraph(graph_analysis: VarAnalysisGraph, vars: Set[LocalVarDecl], blk: Stmt): (Graph[LocalVarDecl, DefaultEdge]/*, Map[LocalVarDecl,LocalVarDecl]*/) = { - var allVertices: Map[LocalVarDecl, LocalVarDecl] = Map[LocalVarDecl, LocalVarDecl]() + var allVertices: Map[LocalVarDecl, LocalVarDecl] = Map.empty /** add heap variables to vertices */ allVertices += (graph_analysis.heap_vertex -> graph_analysis.createInitialVertex(graph_analysis.heap_vertex)) @@ -92,7 +92,7 @@ trait SetGraphComparison extends VarAnalysisSet { /** compare for each variable */ - graphForMethods.foreach((mg) => { + graphForMethods.foreach(mg => { val methodname = mg._1 val graph = mg._2 diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala index 989368df7..5e4f24427 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala @@ -3,16 +3,13 @@ package viper.silver.plugin.standard.reasoning.analysis import org.jgrapht.Graph import org.jgrapht.graph.{AbstractBaseGraph, DefaultDirectedGraph, DefaultEdge} import viper.silver.ast.{AccessPredicate, Apply, Assert, Assume, BinExp, CurrentPerm, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, Fold, ForPerm, FuncApp, Goto, If, Inhale, Int, Label, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Package, Program, Ref, Seqn, Stmt, UnExp, Unfold, While} -import viper.silver.plugin.standard.reasoning.{FlowAnnotation, OldCall, Var} -import viper.silver.plugin.standard.reasoning.{ExistentialElim, UniversalIntro} - +import viper.silver.plugin.standard.reasoning.{ExistentialElim, FlowAnnotation, Heap, OldCall, UniversalIntro, Var} import viper.silver.verifier.{AbstractError, ConsistencyError} import java.io.StringWriter import scala.jdk.CollectionConverters._ - case class VarAnalysisGraph(prog: Program, reportErrorWithMsg: AbstractError => Unit) { @@ -79,7 +76,7 @@ case class VarAnalysisGraph(prog: Program, * @param vertices represent the variables that are in scope * @return an identity graph */ - def createIdentityGraph(vertices: Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl, DefaultEdge] = { + private def createIdentityGraph(vertices: Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl, DefaultEdge] = { val graph: Graph[LocalVarDecl, DefaultEdge] = createEmptyGraph(vertices) for ((v,v_init)<-vertices) { graph.addEdge(v_init, v) @@ -93,8 +90,7 @@ case class VarAnalysisGraph(prog: Program, * @param vertices the vertices representing variables which should be checked * @return graph */ - def addIdentityEdges(graph: Graph[LocalVarDecl,DefaultEdge], vertices:Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl, DefaultEdge] = { - + private def addIdentityEdges(graph: Graph[LocalVarDecl,DefaultEdge], vertices:Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl, DefaultEdge] = { for ((v,v_init)<-vertices) { if (graph.incomingEdgesOf(v).isEmpty) { graph.addEdge(v_init, v, new DefaultEdge) @@ -128,7 +124,7 @@ case class VarAnalysisGraph(prog: Program, * @param exp expressions from which all variables should be returned * @return set of Variable declarations */ - def getVarsFromExpr(graph: Graph[LocalVarDecl, DefaultEdge], exp: Exp): Set[LocalVarDecl] = { + private def getVarsFromExpr(graph: Graph[LocalVarDecl, DefaultEdge], exp: Exp): Set[LocalVarDecl] = { val vars: Set[LocalVarDecl] = Set() exp match { case l@LocalVar(_, _) => @@ -136,7 +132,7 @@ case class VarAnalysisGraph(prog: Program, graph.vertexSet().forEach(v => if (v.name == l.name) { l_decl = v }) - if (l_decl.name == "") { + if (l_decl.name.isEmpty) { l_decl = LocalVarDecl(l.name, l.typ)() } vars + l_decl @@ -147,7 +143,7 @@ case class VarAnalysisGraph(prog: Program, case UnExp(exp) => getVarsFromExpr(graph, exp) - case FuncApp(_,exps) => + case FuncApp(_, exps) => var allVars = vars if (!vars.contains(heap_vertex)) { allVars += heap_vertex @@ -162,7 +158,7 @@ case class VarAnalysisGraph(prog: Program, }) allVars - case DomainFuncApp(_,exps,_) => + case DomainFuncApp(_, exps, _) => var allVars = vars exps.foreach(e => { val exp_vars = getVarsFromExpr(graph, e) @@ -179,8 +175,7 @@ case class VarAnalysisGraph(prog: Program, vars } - - case FieldAccess(v,_) => + case FieldAccess(v, _) => val allVars = vars ++ getVarsFromExpr(graph,v) if(!allVars.contains(heap_vertex)) allVars + heap_vertex @@ -196,8 +191,7 @@ case class VarAnalysisGraph(prog: Program, }) allVars - case _ => - Set() + case _ => Set() } } @@ -207,8 +201,7 @@ case class VarAnalysisGraph(prog: Program, * @return copied graph */ def copyGraph(graph: Graph[LocalVarDecl, DefaultEdge]): Graph[LocalVarDecl, DefaultEdge] = { - val copied_graph = graph.asInstanceOf[AbstractBaseGraph[LocalVarDecl, DefaultEdge]].clone().asInstanceOf[DefaultDirectedGraph[LocalVarDecl, DefaultEdge]] - copied_graph + graph.asInstanceOf[AbstractBaseGraph[LocalVarDecl, DefaultEdge]].clone().asInstanceOf[DefaultDirectedGraph[LocalVarDecl, DefaultEdge]] } /** @@ -217,7 +210,7 @@ case class VarAnalysisGraph(prog: Program, * @param graph2 * @return graph */ - def unionEdges(graph1: Graph[LocalVarDecl, DefaultEdge], graph2: Graph[LocalVarDecl, DefaultEdge]): Graph[LocalVarDecl,DefaultEdge] = { + private def unionEdges(graph1: Graph[LocalVarDecl, DefaultEdge], graph2: Graph[LocalVarDecl, DefaultEdge]): Graph[LocalVarDecl,DefaultEdge] = { val new_graph = copyGraph(graph1) if (graph1.vertexSet().equals(graph2.vertexSet())) { for (e2: DefaultEdge <- graph2.edgeSet().asScala.toSet) { @@ -228,8 +221,7 @@ case class VarAnalysisGraph(prog: Program, } } } else { - /* should not happen */ - () + throw new AssertionError(s"cannot union edges since graphs have different vertex sets") } new_graph } @@ -243,7 +235,7 @@ case class VarAnalysisGraph(prog: Program, * @param vertices * @return merged graph */ - def mergeGraphs(graph1: Graph[LocalVarDecl, DefaultEdge], graph2: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl, LocalVarDecl]): Graph[LocalVarDecl,DefaultEdge] = { + private def mergeGraphs(graph1: Graph[LocalVarDecl, DefaultEdge], graph2: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl, LocalVarDecl]): Graph[LocalVarDecl,DefaultEdge] = { val new_graph = createEmptyGraph(vertices) for (e1: DefaultEdge <- graph1.edgeSet().asScala.toSet) { val src = graph1.getEdgeSource(e1) @@ -254,8 +246,7 @@ case class VarAnalysisGraph(prog: Program, new_graph.addEdge(src, graph2.getEdgeTarget(e2), new DefaultEdge) } } else { - /* should not happen */ - () + throw new AssertionError(s"Vertex not found for declaration $trgt") } } new_graph @@ -270,16 +261,15 @@ case class VarAnalysisGraph(prog: Program, case Seqn(ss, scopedSeqnDeclarations) => var allVertices: Map[LocalVarDecl,LocalVarDecl] = vertices for (d <- scopedSeqnDeclarations) { - if (d.isInstanceOf[LocalVarDecl]) { - val d_decl = d.asInstanceOf[LocalVarDecl] - val d_init = createInitialVertex(d_decl) - allVertices += (d_decl -> d_init) + d match { + case decl: LocalVarDecl => + val d_init = createInitialVertex(decl) + allVertices += (decl -> d_init) } } var new_graph: Graph[LocalVarDecl, DefaultEdge] = createIdentityGraph(allVertices) for (s <- ss) { val comp_graph = compute_graph(s, allVertices) - new_graph = mergeGraphs(new_graph, comp_graph, allVertices) } @@ -299,8 +289,8 @@ case class VarAnalysisGraph(prog: Program, val cond_graph = copyGraph(id_graph) val thn_graph = compute_graph(thn, vertices) val els_graph = compute_graph(els, vertices) - val writtenToThn = getModifiedVars(vertices, thn).getOrElse(Set()) - val writtenToEls = getModifiedVars(vertices, els).getOrElse(Set()) + val writtenToThn = getModifiedVars(vertices, thn).getOrElse(Set.empty) + val writtenToEls = getModifiedVars(vertices, els).getOrElse(Set.empty) val allWrittenTo = writtenToThn ++ writtenToEls for (w <- allWrittenTo) { if (cond_graph.containsVertex(w)) { @@ -314,14 +304,11 @@ case class VarAnalysisGraph(prog: Program, cond_graph.removeEdge(vertices(v),v) }) val thn_els_graph = unionEdges(thn_graph, els_graph) - val res_graph = unionEdges(cond_graph, thn_els_graph) - res_graph + unionEdges(cond_graph, thn_els_graph) case While(cond, _, body) => - /** analyse one iteration of the while loop */ val one_iter_graph: Graph[LocalVarDecl, DefaultEdge] = compute_graph(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), vertices) - var edges_equal: Boolean = false var merge_graph = copyGraph(one_iter_graph) while(!edges_equal) { @@ -332,7 +319,6 @@ case class VarAnalysisGraph(prog: Program, for (e1: DefaultEdge <- last_iter_graph.edgeSet().asScala.toSet) { if (merge_graph.getEdge(last_iter_graph.getEdgeSource(e1), last_iter_graph.getEdgeTarget(e1)) == null) { edges_equal = false - } else { edges_equal = true } @@ -345,7 +331,6 @@ case class VarAnalysisGraph(prog: Program, var new_graph: Graph[LocalVarDecl,DefaultEdge] = createEmptyGraph(vertices) val rhs_vars = getVarsFromExpr(new_graph, rhs) val lhs_decl: LocalVarDecl = LocalVarDecl(lhs.name,lhs.typ)(lhs.pos) - for (v <- rhs_vars) { /** if the variable on the right hand side is a field access */ if (v.equals(heap_vertex)) { @@ -356,7 +341,6 @@ case class VarAnalysisGraph(prog: Program, new_graph.addEdge(v_init, lhs_decl, new DefaultEdge) } } - /** Since variables that are not assigned to should have an edge from their initial value to their 'end'-value */ val vert_wout_lhs = vertices - lhs_decl new_graph = addIdentityEdges(new_graph, vert_wout_lhs) @@ -366,7 +350,6 @@ case class VarAnalysisGraph(prog: Program, val id_graph = createIdentityGraph(vertices) expInfluencesAllVertices(exp, id_graph, vertices) - /** same as inhale */ case Assume(exp) => val id_graph = createIdentityGraph(vertices) @@ -387,20 +370,15 @@ case class VarAnalysisGraph(prog: Program, id_graph } - case Assert(_) => createIdentityGraph(vertices) - - case Label(_, _) => - createIdentityGraph(vertices) + case Label(_, _) => createIdentityGraph(vertices) case MethodCall(methodName, args, targets) => - val met = prog.findMethod(methodName) - val methodcall_graph: Graph[LocalVarDecl, DefaultEdge] = createEmptyGraph(vertices) - + val met = prog.findMethod(methodName) + val methodcall_graph = createEmptyGraph(vertices) createInfluencedByGraph(methodcall_graph,vertices,args,targets,met.formalArgs, met.formalReturns,met.posts) - case FieldAssign(_, rhs) => val id_graph = createIdentityGraph(vertices) val rhs_vars = getVarsFromExpr(id_graph, rhs) @@ -416,8 +394,7 @@ case class VarAnalysisGraph(prog: Program, id_graph /** TODO: technically not implemented correctly */ - case ExistentialElim(_,_,_) => - createIdentityGraph(vertices) + case ExistentialElim(_,_,_) => createIdentityGraph(vertices) case UniversalIntro(varList,_,_,_,blk) => val new_vertices: Map[LocalVarDecl, LocalVarDecl] = vertices ++ varList.map(v => (v -> createInitialVertex(v))) @@ -428,7 +405,6 @@ case class VarAnalysisGraph(prog: Program, }) new_graph - case Fold(acc) => val id_graph = createIdentityGraph(vertices) val vars = getVarsFromExpr(id_graph, acc) @@ -461,43 +437,40 @@ case class VarAnalysisGraph(prog: Program, }) id_graph - case g@Goto(_) => - reportErrorWithMsg(ConsistencyError(s"${g} is an undefined statement for the modular information flow analysis", g.pos)) + case g: Goto => + reportErrorWithMsg(ConsistencyError(s"$g is an undefined statement for the modular information flow analysis", g.pos)) createEmptyGraph(vertices) - case OldCall(methodName,args,targets,_) => + case OldCall(methodName, args, targets, _) => val met = prog.findMethod(methodName) val methodcall_graph: Graph[LocalVarDecl, DefaultEdge] = createEmptyGraph(vertices) createInfluencedByGraph(methodcall_graph, vertices, args, targets, met.formalArgs, met.formalReturns, met.posts) - case s@_ => - reportErrorWithMsg(ConsistencyError(s"${s} is an undefined statement for the modular information flow analysis", s.pos)) + case _ => + reportErrorWithMsg(ConsistencyError(s"$stmt is an undefined statement for the modular information flow analysis", stmt.pos)) createEmptyGraph(vertices) } } - /** * creates an edge between every variable in the expression to every variable that is in scope and returns resulting graph */ - def expInfluencesAllVertices(exp:Exp, graph: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl,LocalVarDecl]) : Graph[LocalVarDecl, DefaultEdge] = { + private def expInfluencesAllVertices(exp:Exp, graph: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl,LocalVarDecl]) : Graph[LocalVarDecl, DefaultEdge] = { val id_graph = createIdentityGraph(vertices) val vars = getVarsFromExpr(graph, exp) vars.foreach(v => { val init_v = vertices(v) - vertices.keySet.foreach(k => { id_graph.addEdge(init_v, k, new DefaultEdge) }) - }) id_graph } /** creates graph for methodcall and oldcall. maps expressions passed to methods to the method arguments, computes the graph based on the flow annotation, * and finally maps the return variables to the variables that the method is assigned to. */ - def createInfluencedByGraph(graph: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl,LocalVarDecl], arg_names: Seq[Exp], ret_names: Seq[LocalVar], method_arg_names: Seq[LocalVarDecl], method_ret_names: Seq[LocalVarDecl], posts: Seq[Exp]): Graph[LocalVarDecl, DefaultEdge] = { + private def createInfluencedByGraph(graph: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl,LocalVarDecl], arg_names: Seq[Exp], ret_names: Seq[LocalVar], method_arg_names: Seq[LocalVarDecl], method_ret_names: Seq[LocalVarDecl], posts: Seq[Exp]): Graph[LocalVarDecl, DefaultEdge] = { /** set of all target variables that have not been included in the influenced by expression up until now */ var retSet: Set[LocalVarDecl] = method_ret_names.toSet + heap_vertex @@ -505,7 +478,7 @@ case class VarAnalysisGraph(prog: Program, val method_arg_names_incl_heap = method_arg_names ++ Seq(heap_vertex) /** create .arg_ declaration for each argument */ - var method_args: Map[LocalVarDecl, LocalVarDecl] = Map() + var method_args: Map[LocalVarDecl, LocalVarDecl] = Map.empty var method_arg_counter: Int = 0 method_arg_names_incl_heap.foreach(method_arg => { method_args += (method_arg -> LocalVarDecl(".arg" + method_arg_counter, method_arg.typ)(method_arg.pos)) @@ -555,7 +528,10 @@ case class VarAnalysisGraph(prog: Program, case FlowAnnotation(returned, arguments) => /** returned has to be instance of LocalVar */ - val returned_var: LocalVar = if (returned.isInstanceOf[Var]) returned.asInstanceOf[Var].var_decl.asInstanceOf[LocalVar] else heap_vertex.localVar + val returned_var: LocalVar = returned match { + case v: Var => v.var_decl.asInstanceOf[LocalVar] + case _: Heap => heap_vertex.localVar + } /** create LocalVarDecl such that it can be added in the graph */ val return_decl = LocalVarDecl(returned_var.name, returned_var.typ)(returned_var.pos) retSet -= return_decl @@ -566,15 +542,17 @@ case class VarAnalysisGraph(prog: Program, arguments.foreach(argument => { /** argument has to be instance of LocalVar */ - val argument_var: LocalVar = if (argument.isInstanceOf[Var]) argument.asInstanceOf[Var].var_decl.asInstanceOf[LocalVar] else heap_vertex.localVar + val argument_var: LocalVar = argument match { + case v: Var => v.var_decl.asInstanceOf[LocalVar] + case _: Heap => heap_vertex.localVar + } val argument_decl = LocalVarDecl(argument_var.name, argument_var.typ)(argument_var.pos) /** get corresponding .arg variable and add edge from .arg to .ret vertex */ val prov_decl = method_args(argument_decl) methodcall_graph.addEdge(prov_decl, method_rets(return_decl), new DefaultEdge) }) - case _ => () - + case _ => } /** now need to add to graph the edges from the method return variables to the target variables */ @@ -670,9 +648,9 @@ case class VarAnalysisGraph(prog: Program, case None => output = getModifiedVars(vertices, s) case Some(v) => output = Some(v ++ getModifiedVars(vertices, s).getOrElse(Set[LocalVarDecl]())) } - } output + case LocalVarAssign(lhs, _) => var res: Option[Set[LocalVarDecl]] = None for (vs <- vertices) { @@ -684,9 +662,10 @@ case class VarAnalysisGraph(prog: Program, } } res + case If(_, thn, els) => - val writtenThn: Option[Set[LocalVarDecl]] = getModifiedVars(vertices, thn) - val writtenEls: Option[Set[LocalVarDecl]] = getModifiedVars(vertices, els) + val writtenThn = getModifiedVars(vertices, thn) + val writtenEls = getModifiedVars(vertices, els) (writtenThn, writtenEls) match { case (None, None) => None case (Some(_), None) => writtenThn @@ -697,21 +676,11 @@ case class VarAnalysisGraph(prog: Program, case While(_, _, body) => getModifiedVars(vertices, body) - case MethodCall(_, _, _) => - None - - - case Inhale(_) => - None - - case Assume(_) => - None - - case Label(_, _) => - None - - case _ => - None + case MethodCall(_, _, _) => None + case Inhale(_) => None + case Assume(_) => None + case Label(_, _) => None + case _ => None } } -} \ No newline at end of file +} diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala index b357434d1..edfdfa05d 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala @@ -1,6 +1,6 @@ package viper.silver.plugin.standard.reasoning.analysis -import viper.silver.ast.{Assume, BinExp, Declaration, Exp, FieldAssign, If, Inhale, Label, Literal, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Seqn, Stmt, UnExp, While} +import viper.silver.ast.{Assume, BinExp, Declaration, Exp, FieldAssign, If, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Seqn, Stmt, UnExp, While} import viper.silver.plugin.standard.reasoning.UniversalIntro import viper.silver.verifier.{AbstractError, ConsistencyError} @@ -21,11 +21,10 @@ trait VarAnalysisSet { vars_outside_blk --= mutable.Set(u.transitiveScopedDecls: _*) /** check whether any variables were tainted that are declared outside of our block */ - if (!(vars_outside_blk.intersect(all_tainted).isEmpty)) { + if (vars_outside_blk.intersect(all_tainted).nonEmpty) { val tainted_vars: Set[Declaration] = all_tainted.intersect(vars_outside_blk) val problem_vars: String = tainted_vars.mkString(", ") - val problem_pos: String = tainted_vars.map(vs => vs.pos).mkString(", ") - reportError(ConsistencyError("Universal introduction variable might have been assigned to variable " + problem_vars/* + " at positions (" + problem_pos + ")*/+ ", defined outside of the block", u.pos)) + reportError(ConsistencyError(s"Universal introduction variable might have been assigned to variable $problem_vars, defined outside of the block", u.pos)) } } @@ -38,12 +37,11 @@ trait VarAnalysisSet { def get_tainted_vars_stmt(tainted: Set[Declaration], stmt: Stmt): Set[Declaration] = { var output: Set[Declaration] = tainted stmt match { - case Seqn(ss, decls) => { + case Seqn(ss, _) => for (s <- ss) { output = get_tainted_vars_stmt(output, s) } output - } case LocalVarAssign(lhs, rhs) => if (is_expr_tainted(tainted, rhs)) { tainted ++ Set(LocalVarDecl(lhs.name, lhs.typ)(stmt.pos)) @@ -51,57 +49,47 @@ trait VarAnalysisSet { tainted -- Set(LocalVarDecl(lhs.name, lhs.typ)(stmt.pos)) } - case If(cond, thn, els) => { + case If(cond, thn, els) => if (is_expr_tainted(tainted, cond)) { - val written_vars_thn: Option[Set[LocalVarDecl]] = writtenTo(thn) - val written_vars_els: Option[Set[LocalVarDecl]] = writtenTo(els) + val written_vars_thn = writtenTo(thn) + val written_vars_els = writtenTo(els) Set[Declaration]() ++ written_vars_thn.getOrElse(mutable.Set()) ++ written_vars_els.getOrElse(mutable.Set()) } else { get_tainted_vars_stmt(tainted, thn) ++ get_tainted_vars_stmt(tainted, els) } - } - case w@While(cond, _, body) => { + case w@While(cond, _, body) => val new_tainted = get_tainted_vars_stmt(tainted, If(cond,body,Seqn(Seq(), Seq())(body.pos))(body.pos)) if (new_tainted.equals(tainted)) { tainted } else { get_tainted_vars_stmt(new_tainted, w) } - } - case m@MethodCall(_, _, _) => { + case m@MethodCall(_, _, _) => reportErrorWithMsg(ConsistencyError("Might be not allowed method call inside universal introduction block", m.pos)) tainted - } - case i@Inhale(exp) => { + case i@Inhale(exp) => if(exp.isPure) { tainted } else { reportErrorWithMsg(ConsistencyError("There might be an impure inhale expression inside universal introduction block", i.pos)) tainted } - } - case Assume(_) => { - tainted - } + case Assume(_) => tainted - case Label(_, _) => { - tainted - } + case Label(_, _) => tainted /** TODO: Do not allow Heap assignments */ - case f@FieldAssign(lhs, rhs) => { + case f@FieldAssign(_, _) => reportErrorWithMsg(ConsistencyError("FieldAssign for modular flow analysis with sets", f.pos)) tainted - } - case s@_ => { + case s@_ => reportErrorWithMsg(ConsistencyError("undefined statement for modular flow analysis with set", s.pos)) tainted - } } } @@ -112,17 +100,11 @@ trait VarAnalysisSet { * @param exp * @return */ - def addExprToTainted(tainted: Set[Declaration], exp: Exp) : Set[Declaration] = { + private def addExprToTainted(tainted: Set[Declaration], exp: Exp) : Set[Declaration] = { exp match { - case l@LocalVar(_, _) => { - tainted ++ Set(LocalVarDecl(l.name, l.typ)(exp.pos)) - } - case BinExp(exp1, exp2) => { - addExprToTainted(tainted, exp1) ++ addExprToTainted(tainted, exp2) - } - case UnExp(exp) => { - addExprToTainted(tainted, exp) - } + case l@LocalVar(_, _) => tainted ++ Set(LocalVarDecl(l.name, l.typ)(exp.pos)) + case BinExp(exp1, exp2) => addExprToTainted(tainted, exp1) ++ addExprToTainted(tainted, exp2) + case UnExp(exp) => addExprToTainted(tainted, exp) case _ => tainted } } @@ -133,22 +115,16 @@ trait VarAnalysisSet { * @param exp * @return */ - def is_expr_tainted(tainted:Set[Declaration], exp:Exp) : Boolean = { + private def is_expr_tainted(tainted:Set[Declaration], exp:Exp) : Boolean = { exp match { - case l@LocalVar(_, _) => { - isTainted(LocalVarDecl(l.name, l.typ)(l.pos), tainted) - } - case BinExp(exp1, exp2) => { - is_expr_tainted(tainted, exp1) || is_expr_tainted(tainted, exp2) - } - case UnExp(exp) => { - is_expr_tainted(tainted, exp) - } + case l@LocalVar(_, _) => isTainted(LocalVarDecl(l.name, l.typ)(l.pos), tainted) + case BinExp(exp1, exp2) => is_expr_tainted(tainted, exp1) || is_expr_tainted(tainted, exp2) + case UnExp(exp) => is_expr_tainted(tainted, exp) case _ => false } } - def isTainted(name:Declaration, tainted:Set[Declaration]) : Boolean = { + private def isTainted(name:Declaration, tainted:Set[Declaration]): Boolean = { tainted.contains(name) } @@ -160,56 +136,37 @@ trait VarAnalysisSet { def writtenTo(stmt: Stmt): Option[Set[LocalVarDecl]] = { var output: Option[Set[LocalVarDecl]] = None stmt match { - case Seqn(ss, _) => { + case Seqn(ss, _) => for (s <- ss) { output match { case None => output = writtenTo(s) case Some(v) => output = Some(v ++ writtenTo(s).getOrElse(Set[LocalVarDecl]())) } - } output - } - case LocalVarAssign(lhs, _) => { + + case LocalVarAssign(lhs, _) => val lhs_var = LocalVarDecl(lhs.name, lhs.typ)(lhs.pos) Some(Set(lhs_var)) - } - case If(_, thn, els) => { - val writtenThn: Option[Set[LocalVarDecl]] = writtenTo(thn) - val writtenEls: Option[Set[LocalVarDecl]] = writtenTo(els) + + case If(_, thn, els) => + val writtenThn = writtenTo(thn) + val writtenEls = writtenTo(els) (writtenThn, writtenEls) match { case (None,None) => None case (Some(_), None) => writtenThn case (None, Some(_)) => writtenEls case (Some(t), Some(e)) => Some(t++e) } - } - case While(_, _, body) => { - writtenTo(body) - } + case While(_, _, body) => writtenTo(body) /** TODO */ - case MethodCall(_, _, _) => { - None - } - - - case Inhale(_) => { - None - } - - case Assume(_) => { - None - } - - case Label(_, _) => { - None - } - - case _ => { - None - } + case MethodCall(_, _, _) => None + case Inhale(_) => None + case Assume(_) => None + case Label(_, _) => None + case _ => None } } } diff --git a/src/main/scala/viper/silver/reporter/Message.scala b/src/main/scala/viper/silver/reporter/Message.scala index 441810eee..b38d21679 100644 --- a/src/main/scala/viper/silver/reporter/Message.scala +++ b/src/main/scala/viper/silver/reporter/Message.scala @@ -8,7 +8,7 @@ package viper.silver.reporter import viper.silver.reporter.BackendSubProcessStages.BackendSubProcessStage import viper.silver.verifier._ -import viper.silver.ast.{QuantifiedExp, Trigger} +import viper.silver.ast.{Program, QuantifiedExp, Trigger} import viper.silver.parser.PProgram /** @@ -326,3 +326,8 @@ case class VerificationTerminationMessage() extends Message { override val toString: String = "verification_termination_message" override val name: String = "verification_termination_message" } + +case class PluginTransformationsAppliedMessage(program: Program) extends Message { + override val toString: String = "plugin_transformations_applied_message" + override val name: String = "plugin_transformations_applied_message" +} diff --git a/src/main/scala/viper/silver/reporter/Reporter.scala b/src/main/scala/viper/silver/reporter/Reporter.scala index 0c51bd50d..a7ca71937 100644 --- a/src/main/scala/viper/silver/reporter/Reporter.scala +++ b/src/main/scala/viper/silver/reporter/Reporter.scala @@ -6,6 +6,8 @@ package viper.silver.reporter +import viper.silver.ast.pretty.FastPrettyPrinter + import java.io.FileWriter import scala.collection.mutable._ @@ -82,7 +84,8 @@ case class CSVReporter(name: String = "csv_reporter", path: String = "report.csv } } -case class StdIOReporter(name: String = "stdout_reporter", timeInfo: Boolean = true) extends Reporter { +trait StdIOReporterTrait extends Reporter { + def timeInfo: Boolean var counter = 0 @@ -195,6 +198,24 @@ case class StdIOReporter(name: String = "stdout_reporter", timeInfo: Boolean = t } } +case class StdIOReporter(name: String = "stdout_reporter", timeInfo: Boolean = true) extends StdIOReporterTrait + +case class TransformationReporter(name: String = "transformation_reporter", path: String = "transformed.vpr", timeInfo: Boolean = true) extends StdIOReporterTrait { + def this() = this("transformation_reporter", "transformed.vpr", true) + + override def report(msg: Message): Unit = { + msg match { + case PluginTransformationsAppliedMessage(program) => + val file = new FileWriter(path, false) + file.write(FastPrettyPrinter.pretty(program)) + file.flush() + file.close() + case _ => + } + super.report(msg) + } +} + case class PollingReporter(name: String = "polling_reporter", pass_through_reporter: Reporter) extends Reporter { // this reporter stores the messages it receives and reports them upon polling var messages: Queue[Message] = Queue() diff --git a/src/test/resources/reasoning/existential_elim.vpr b/src/test/resources/reasoning/existential_elim.vpr index 0b849bb06..53fc84f64 100644 --- a/src/test/resources/reasoning/existential_elim.vpr +++ b/src/test/resources/reasoning/existential_elim.vpr @@ -14,7 +14,7 @@ field f: Int method ex2() { - //:: ExpectedOutput(consistency.error) + //:: ExpectedOutput(typechecker.error) obtain x: Ref where {x.f} acc(x.f) } diff --git a/src/test/resources/reasoning/immutableVar.vpr b/src/test/resources/reasoning/immutableVar.vpr index 3d5b5f6dd..67556dc54 100644 --- a/src/test/resources/reasoning/immutableVar.vpr +++ b/src/test/resources/reasoning/immutableVar.vpr @@ -12,7 +12,7 @@ method xassigned() //:: ExpectedOutput(consistency.error) prove forall x:Int {P(x)} assuming P(x) implies Q(x) { var y:Int := x+1 - x:=2 + x:=2 } } diff --git a/src/test/resources/reasoning/old_call.vpr b/src/test/resources/reasoning/old_call.vpr new file mode 100644 index 000000000..55246600e --- /dev/null +++ b/src/test/resources/reasoning/old_call.vpr @@ -0,0 +1,12 @@ +method foo() +//:: ExpectedOutput(typechecker.error) +requires oldCall[l](lemma()) +{ + var x: Int + x := oldCall[l](lemma()) + //:: ExpectedOutput(typechecker.error) + x := 42 + oldCall[l](lemma()) +} + +method lemma() returns (x: Int) +isLemma From abe8a9c529f8b2722d5c95b992869f7a3f1277c6 Mon Sep 17 00:00:00 2001 From: Linard Arquint Date: Tue, 27 Feb 2024 17:51:45 +0100 Subject: [PATCH 09/41] reduces diff --- build.sbt | 2 +- .../reasoning/ReasoningPASTExtension.scala | 2 +- .../scala/viper/silver/reporter/Message.scala | 7 +- .../viper/silver/reporter/Reporter.scala | 23 +- .../resources/all/basic/backend_types.vpr | 60 ++++ .../all/basic/backend_types_consistency.vpr | 7 + src/test/resources/all/issues/carbon/0239.vpr | 2 +- .../resources/all/issues/silicon/0369.vpr | 9 + .../resources/all/issues/silicon/0376.vpr | 32 ++ .../resources/all/issues/silicon/0554.vpr | 81 +++++ .../resources/all/issues/silicon/0560b.vpr | 129 ++++++++ .../resources/all/issues/silicon/0567.vpr | 3 + .../resources/all/issues/silicon/0595.vpr | 3 + .../resources/all/issues/silicon/0601.vpr | 3 + .../resources/all/issues/silicon/0630.vpr | 3 + .../resources/all/issues/silicon/0641.vpr | 3 + .../resources/all/issues/silicon/0648.vpr | 32 ++ .../resources/all/issues/silicon/0652.vpr | 48 +++ .../resources/all/issues/silicon/0678.vpr | 17 ++ src/test/resources/all/issues/silver/0207.vpr | 2 +- src/test/resources/all/issues/silver/0586.vpr | 3 + src/test/resources/all/issues/silver/0637.vpr | 41 +++ src/test/resources/all/issues/silver/0639.vpr | 15 + .../benchmarks/generate_benchmarks.py | 276 ++++++++++++++++++ 24 files changed, 771 insertions(+), 32 deletions(-) create mode 100644 src/test/resources/all/basic/backend_types.vpr create mode 100644 src/test/resources/all/basic/backend_types_consistency.vpr create mode 100644 src/test/resources/all/issues/silicon/0369.vpr create mode 100644 src/test/resources/all/issues/silicon/0376.vpr create mode 100644 src/test/resources/all/issues/silicon/0554.vpr create mode 100644 src/test/resources/all/issues/silicon/0560b.vpr create mode 100644 src/test/resources/all/issues/silicon/0648.vpr create mode 100644 src/test/resources/all/issues/silicon/0652.vpr create mode 100644 src/test/resources/all/issues/silicon/0678.vpr create mode 100644 src/test/resources/all/issues/silver/0637.vpr create mode 100644 src/test/resources/all/issues/silver/0639.vpr create mode 100644 src/test/resources/quasihavoc/benchmarks/generate_benchmarks.py diff --git a/build.sbt b/build.sbt index b6891cddb..741a51f7c 100644 --- a/build.sbt +++ b/build.sbt @@ -48,7 +48,7 @@ lazy val silver = (project in file(".")) libraryDependencies += "org.scalatest" %% "scalatest" % "3.1.2", // Testing libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2", // Parsing libraryDependencies += "com.lihaoyi" %% "fastparse" % "2.2.2", // Parsing - libraryDependencies += "org.rogach" %% "scallop" % "4.0.1", // CLI parsing + libraryDependencies += "org.rogach" %% "scallop" % "4.0.4", // CLI parsing libraryDependencies += "commons-io" % "commons-io" % "2.8.0", // I/O libraryDependencies += "com.google.guava" % "guava" % "29.0-jre", // Collections libraryDependencies += "org.jgrapht" % "jgrapht-core" % "1.5.0", // Graphs diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala index ffb029691..949a60f2e 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -12,7 +12,7 @@ case object PImpliesKeyword extends PKw("implies") with PKeywordLang case object PInfluencedKeyword extends PKw("influenced") with PKeywordLang with PKw.PostSpec case object PByKeyword extends PKw("by") with PKeywordLang case object PHeapKeyword extends PKw("heap") with PKeywordLang -case object PIsLemmaKeyword extends PKw("isLemma") with PKeywordLang with PKw.AnySpec +case object PIsLemmaKeyword extends PKw("isLemma") with PKeywordLang with PKw.MethodSpec case object POldCallKeyword extends PKw("oldCall") with PKeywordLang with PKeywordStmt case class PExistentialElim(obtainKw: PReserved[PObtainKeyword.type], delimitedVarDecls: PDelimited[PLocalVarDecl, PSym.Comma], whereKw: PReserved[PWhereKeyword.type], trig: Seq[PTrigger], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { diff --git a/src/main/scala/viper/silver/reporter/Message.scala b/src/main/scala/viper/silver/reporter/Message.scala index b38d21679..441810eee 100644 --- a/src/main/scala/viper/silver/reporter/Message.scala +++ b/src/main/scala/viper/silver/reporter/Message.scala @@ -8,7 +8,7 @@ package viper.silver.reporter import viper.silver.reporter.BackendSubProcessStages.BackendSubProcessStage import viper.silver.verifier._ -import viper.silver.ast.{Program, QuantifiedExp, Trigger} +import viper.silver.ast.{QuantifiedExp, Trigger} import viper.silver.parser.PProgram /** @@ -326,8 +326,3 @@ case class VerificationTerminationMessage() extends Message { override val toString: String = "verification_termination_message" override val name: String = "verification_termination_message" } - -case class PluginTransformationsAppliedMessage(program: Program) extends Message { - override val toString: String = "plugin_transformations_applied_message" - override val name: String = "plugin_transformations_applied_message" -} diff --git a/src/main/scala/viper/silver/reporter/Reporter.scala b/src/main/scala/viper/silver/reporter/Reporter.scala index a7ca71937..0c51bd50d 100644 --- a/src/main/scala/viper/silver/reporter/Reporter.scala +++ b/src/main/scala/viper/silver/reporter/Reporter.scala @@ -6,8 +6,6 @@ package viper.silver.reporter -import viper.silver.ast.pretty.FastPrettyPrinter - import java.io.FileWriter import scala.collection.mutable._ @@ -84,8 +82,7 @@ case class CSVReporter(name: String = "csv_reporter", path: String = "report.csv } } -trait StdIOReporterTrait extends Reporter { - def timeInfo: Boolean +case class StdIOReporter(name: String = "stdout_reporter", timeInfo: Boolean = true) extends Reporter { var counter = 0 @@ -198,24 +195,6 @@ trait StdIOReporterTrait extends Reporter { } } -case class StdIOReporter(name: String = "stdout_reporter", timeInfo: Boolean = true) extends StdIOReporterTrait - -case class TransformationReporter(name: String = "transformation_reporter", path: String = "transformed.vpr", timeInfo: Boolean = true) extends StdIOReporterTrait { - def this() = this("transformation_reporter", "transformed.vpr", true) - - override def report(msg: Message): Unit = { - msg match { - case PluginTransformationsAppliedMessage(program) => - val file = new FileWriter(path, false) - file.write(FastPrettyPrinter.pretty(program)) - file.flush() - file.close() - case _ => - } - super.report(msg) - } -} - case class PollingReporter(name: String = "polling_reporter", pass_through_reporter: Reporter) extends Reporter { // this reporter stores the messages it receives and reports them upon polling var messages: Queue[Message] = Queue() diff --git a/src/test/resources/all/basic/backend_types.vpr b/src/test/resources/all/basic/backend_types.vpr new file mode 100644 index 000000000..70f00125a --- /dev/null +++ b/src/test/resources/all/basic/backend_types.vpr @@ -0,0 +1,60 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + + +domain myBV interpretation (SMTLIB: "(_ BitVec 32)", Boogie: "bv32") { + function toBV32(i: Int): myBV interpretation "(_ int2bv 32)" +} + +domain myFloat interpretation (Boogie: "float24e8", SMTLIB: "(_ FloatingPoint 8 24)") { + function tofp(bv: myBV): myFloat interpretation "(_ to_fp 8 24)" + function fp_eq(myFloat, myFloat): Bool interpretation "fp.eq" + + function fp_min(f1: myFloat, f2: myFloat): myFloat interpretation "fp.min" + function fp_max(f1: myFloat, f2: myFloat): myFloat interpretation "fp.max" + function add(d1: myFloat, f2: myFloat): myFloat interpretation "fp.add RNE" + function gt(myFloat, myFloat): Bool interpretation "fp.gt" +} + +field ff: myFloat + + +method test() +{ + var tmp: myBV + tmp := toBV32(1081081856) + var r: Ref + r := new(ff) + r.ff := tofp(toBV32(1103888384)) + var fs : Seq[myFloat] + fs := Seq(tofp(toBV32(1081081856))) + assert fp_eq(fp_min(tofp(tmp), r.ff), fs[0]) && + fp_eq(fp_max(tofp(toBV32(1081081856)), tofp(toBV32(1103888384))), tofp(toBV32(1103888384))) + + //:: ExpectedOutput(assert.failed:assertion.false) + assert fp_eq(fp_min(tofp(tmp), r.ff), fs[0]) && + fp_eq(fp_max(tofp(toBV32(1081081856)), tofp(toBV32(1103888384))), tofp(toBV32(110388838))) +} + + +method testOp() +{ + var first: myFloat + var second: myFloat + var res: myFloat + var zero: myFloat + + first := tofp(toBV32(1081081856)) + second := tofp(toBV32(1103888384)) + res := tofp(toBV32(1105854464)) + + zero := tofp(toBV32(0)) + var addition: myFloat + addition := add(first, second) + var result_addition: myFloat + result_addition := add(res, zero) + + assert addition == result_addition + //:: ExpectedOutput(assert.failed:assertion.false) + assert gt(addition, result_addition) +} \ No newline at end of file diff --git a/src/test/resources/all/basic/backend_types_consistency.vpr b/src/test/resources/all/basic/backend_types_consistency.vpr new file mode 100644 index 000000000..42d848051 --- /dev/null +++ b/src/test/resources/all/basic/backend_types_consistency.vpr @@ -0,0 +1,7 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +//:: ExpectedOutput(consistency.error) +domain myBV interpretation (FantasyBackend: "(_ BitVec 32)", DoesntExist: "bv32") { + function toBV32(i: Int): myBV interpretation "(_ int2bv 32)" +} \ No newline at end of file diff --git a/src/test/resources/all/issues/carbon/0239.vpr b/src/test/resources/all/issues/carbon/0239.vpr index 8c709272b..ec8e6a95e 100644 --- a/src/test/resources/all/issues/carbon/0239.vpr +++ b/src/test/resources/all/issues/carbon/0239.vpr @@ -10,9 +10,9 @@ function bool2Ref(b: Bool) : Ref method m(x:Ref) requires forall b:Bool :: acc(bool2Ref(b).val) { - //:: MissingOutput(assignment.failed:insufficient.permission, /silicon/issue/342/) //:: ExpectedOutput(assignment.failed:insufficient.permission) x.val := 42 + //:: MissingOutput(assert.failed:assertion.false, /silicon/issue/34/) //:: ExpectedOutput(assert.failed:assertion.false) assert false } diff --git a/src/test/resources/all/issues/silicon/0369.vpr b/src/test/resources/all/issues/silicon/0369.vpr new file mode 100644 index 000000000..6ac3394d7 --- /dev/null +++ b/src/test/resources/all/issues/silicon/0369.vpr @@ -0,0 +1,9 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +function test() : Int + //:: ExpectedOutput(postcondition.violated:assertion.false) + ensures false +{ + 42 +} \ No newline at end of file diff --git a/src/test/resources/all/issues/silicon/0376.vpr b/src/test/resources/all/issues/silicon/0376.vpr new file mode 100644 index 000000000..29d1c0842 --- /dev/null +++ b/src/test/resources/all/issues/silicon/0376.vpr @@ -0,0 +1,32 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +function Void$discriminant(self: Ref): Int + requires acc(Void(self)) + ensures false +{ + unfolding acc(Void(self)) in 8 +} + +predicate Void(self: Ref) { + false +} + +method m_void$$unreachable$opensqu$0$closesqu$() returns (_0: Ref) +{ + //:: ExpectedOutput(assert.failed:assertion.false) + assert false +} + + +field f: Int + +function foo(x: Ref): Int + requires acc(x.f) && acc(x.f) + ensures false +{ x.f } + +method m() { + //:: ExpectedOutput(assert.failed:assertion.false) + assert false +} \ No newline at end of file diff --git a/src/test/resources/all/issues/silicon/0554.vpr b/src/test/resources/all/issues/silicon/0554.vpr new file mode 100644 index 000000000..8c80883fb --- /dev/null +++ b/src/test/resources/all/issues/silicon/0554.vpr @@ -0,0 +1,81 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +field data: Int +field next_child: Ref +field next_sibling: Ref + +predicate tree(this: Ref) { + acc(this.data) && acc(this.next_child) && acc(this.next_sibling) && + (this.next_child != null ==> tree(this.next_child)) && + (this.next_sibling != null ==> tree(this.next_sibling)) + && ((this.next_child == null && this.next_sibling == null) || (this.next_child != null && this.next_sibling != null)) // remove this clause and the assert fails +} + +method treeTest() +{ + var t : Ref + t := new(*) + t.next_child := null + t.next_sibling := null + fold tree(t) + + var t0 : Ref + t0 := new(*) + t0.next_child := null + t0.next_sibling := null + fold tree(t0) + + var t1 : Ref + t1 := new(*) + t1.next_child := t + t1.next_sibling := t0 + fold tree(t1) + + var t2 : Ref + t2 := new(*) + t2.next_child := null + t2.next_sibling := null + fold tree(t2) + + var t5: Ref + inhale tree(t5) + //assert unfolding tree(t5) in true + + var ctr : Ref + ctr := cloneTree(t5) + //:: ExpectedOutput(assert.failed:assertion.false) + assert false // this verifies! +} + +method cloneTree(this: Ref) returns (res: Ref) + requires acc(tree(this), 1/2) + ensures acc(tree(this), 1/2) && tree(res) + //:: ExpectedOutput(postcondition.violated:assertion.false) + ensures (unfolding acc(tree(this), 1/2) in + unfolding acc(tree(res), write) in // change write to 1/2 and assert false fails + ((this.next_child == res.next_child) && (this.next_sibling == res.next_sibling))) // this postcondition doesn't hold in the else-case, but still verifies +{ + res := new(*) + + unfold acc(tree(this), 1/2) + + if(this.next_child == null && this.next_sibling == null) { + res.next_child := null + res.next_sibling := null + + } else { + + var tmp_child: Ref + var tmp_sibling: Ref + tmp_child := cloneTree(this.next_child) + + + tmp_sibling := cloneTree(this.next_sibling) + res.data := this.data + res.next_child := tmp_child + res.next_sibling := tmp_sibling + } + fold acc(tree(this), 1/2) + fold tree(res) +} \ No newline at end of file diff --git a/src/test/resources/all/issues/silicon/0560b.vpr b/src/test/resources/all/issues/silicon/0560b.vpr new file mode 100644 index 000000000..83b665e6c --- /dev/null +++ b/src/test/resources/all/issues/silicon/0560b.vpr @@ -0,0 +1,129 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + + +domain Tree { + + // Constructors + function Leaf(): Tree + function Node(v: Int, lft: Tree, rgt: Tree): Tree + + function isLeaf(t: Tree): Bool + function isNode(t: Tree): Bool + + axiom type_is_leaf { + forall t: Tree :: + isLeaf(t) == (type(t) == type_Leaf()) + } + + axiom construct_over_destruct_Leaf { + forall t: Tree :: + isLeaf(t) ==> + t == Leaf() + } + + axiom type_is_node { + forall t: Tree :: + isNode(t) == (type(t) == type_Node()) + } + + axiom construct_over_destruct_Node { + forall t: Tree :: + isNode(t) ==> + t == Node(getVal(t), getLeft(t), getRight(t)) + } + + // Deconstructors + function getVal(t: Tree): Int + function getLeft(t: Tree): Tree + function getRight(t: Tree): Tree + + axiom destruct_over_construct { + forall v: Int, lft: Tree, rgt: Tree :: + getVal(Node(v, lft, rgt)) == v + && getLeft(Node(v, lft, rgt)) == lft + && getRight(Node(v, lft, rgt)) == rgt + } + + // Types + function type(t: Tree): Int + unique function type_Leaf(): Int + unique function type_Node(): Int + + axiom type_of_Leaf { + type(Leaf()) == type_Leaf() + } + + axiom type_of_Node { + forall v: Int, lft: Tree, rgt: Tree :: + type(Node(v, lft, rgt)) == type_Node() + } + + axiom all_types { + forall t: Tree :: + (type(t) == type_Leaf() && t == Leaf()) + || (type(t) == type_Node() && exists v: Int, lft: Tree, rgt: Tree :: t == Node(v, lft, rgt)) + } +} + +function height(t: Tree): Int +{ + isLeaf(t) ? 0 : + (height(getLeft(t)) > height(getRight(t)) ? height(getLeft(t)) + 1 : height(getRight(t)) + 1) +} + + +method computeTreeHeight(t: Tree) returns (res: Int) + ensures res == height(t) +{ + if (isLeaf(t)){ + res := 0 + }else{ + var current: Seq[Tree] + var next: Seq[Tree] + current := Seq(t) + next := Seq[Tree]() + res := 0 + + while (|current| > 0) + //:: MissingOutput(invariant.not.preserved:assertion.false, /silicon/issue/34/) + //:: ExpectedOutput(invariant.not.preserved:assertion.false) + invariant |current| == 0 ==> res == height(t) + { + res := res + 1 + next := Seq[Tree]() + + while (|current| > 0) + //:: MissingOutput(invariant.not.preserved:assertion.false, /silicon/issue/34/) + //:: ExpectedOutput(invariant.not.preserved:assertion.false) + //:: ExpectedOutput(invariant.not.established:assertion.false) + invariant forall i: Int :: i >= 0 && i < |current| && + (forall k: Int :: k >= 0 && k < |current| && + height(current[i]) > height(current[k])) && + forall j: Int :: j >= 0 && j < |next| && + (forall k: Int :: k >= 0 && k < |next| && + height(next[j]) > height(next[k])) + ==> height(next[j]) == height(current[i]) - 1 + { + var node : Tree := current[0] + POP(current) + if (isNode(getLeft(node))){ + PUSH(next, getLeft(node)) + } + if (isNode(getRight(node))){ + PUSH(next, getRight(node)) + } + } + current := next + + } + } +} + +define PUSH(stck, v) { + stck := Seq(v) ++ stck +} + +define POP(stck) { + stck := stck[1..] +} \ No newline at end of file diff --git a/src/test/resources/all/issues/silicon/0567.vpr b/src/test/resources/all/issues/silicon/0567.vpr index 2390d8aeb..dc0e099e0 100644 --- a/src/test/resources/all/issues/silicon/0567.vpr +++ b/src/test/resources/all/issues/silicon/0567.vpr @@ -1,3 +1,6 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + function id(i: Int): Int { i } method test01(i: Int) diff --git a/src/test/resources/all/issues/silicon/0595.vpr b/src/test/resources/all/issues/silicon/0595.vpr index 79541ff1e..7105b18a1 100644 --- a/src/test/resources/all/issues/silicon/0595.vpr +++ b/src/test/resources/all/issues/silicon/0595.vpr @@ -1,3 +1,6 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + // Side remark: none of these quantifiers admit any (natural) triggers predicate P() diff --git a/src/test/resources/all/issues/silicon/0601.vpr b/src/test/resources/all/issues/silicon/0601.vpr index fa14e831e..78df23234 100644 --- a/src/test/resources/all/issues/silicon/0601.vpr +++ b/src/test/resources/all/issues/silicon/0601.vpr @@ -1,3 +1,6 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + method test00() { assert Seq(111)[0] != Seq(222)[0] } diff --git a/src/test/resources/all/issues/silicon/0630.vpr b/src/test/resources/all/issues/silicon/0630.vpr index 48d833660..023af6f13 100644 --- a/src/test/resources/all/issues/silicon/0630.vpr +++ b/src/test/resources/all/issues/silicon/0630.vpr @@ -1,3 +1,6 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + field discriminant: Int field field1: Ref diff --git a/src/test/resources/all/issues/silicon/0641.vpr b/src/test/resources/all/issues/silicon/0641.vpr index c8d9bf334..7d50ded1f 100644 --- a/src/test/resources/all/issues/silicon/0641.vpr +++ b/src/test/resources/all/issues/silicon/0641.vpr @@ -1,3 +1,6 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + field f: Int method bar(x: Seq[Ref]) diff --git a/src/test/resources/all/issues/silicon/0648.vpr b/src/test/resources/all/issues/silicon/0648.vpr new file mode 100644 index 000000000..043e247cd --- /dev/null +++ b/src/test/resources/all/issues/silicon/0648.vpr @@ -0,0 +1,32 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +domain array { + function loc(a: array, i: Int): Ref + function len(a: array): Int + function loc_inv_1(loc: Ref): array + function loc_inv_2(loc: Ref): Int + + axiom { + (forall a: array, i: Int :: { loc(a, i) } loc_inv_1(loc(a, i)) == a && loc_inv_2(loc(a, i)) == i) + } + + axiom { + (forall a: array :: { len(a) } len(a) >= 0) + } +} + +field int: Int + +function correctness_upto(parent: array, left: array, root: Int): Bool + requires (forall i: Int :: 0 <= i && i < len(left) ==> acc(loc(left, i).int, 1/2)) + requires (forall i: Int :: 0 <= i && i < len(parent) ==> acc(loc(parent, i).int, 1/2)) + requires correctness_invar(left) +{ + (forall i: Int :: { loc(parent, i).int } 0 <= i && i < len(parent) ==> loc(parent, i).int == 0) && + (forall i: Int :: { loc(parent, i).int } 0 <= i && i < len(parent) ==> loc(parent, i).int == 0) +} + +function correctness_invar(left: array): Bool + requires (forall i: Int :: { loc(left, i).int } (0 <= i && i < len(left)) ==> acc(loc(left, i).int, 1/2)) +{ true } \ No newline at end of file diff --git a/src/test/resources/all/issues/silicon/0652.vpr b/src/test/resources/all/issues/silicon/0652.vpr new file mode 100644 index 000000000..7c17e7a1a --- /dev/null +++ b/src/test/resources/all/issues/silicon/0652.vpr @@ -0,0 +1,48 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +domain array { + function array_loc(a: array, i: Int): Ref + function alen(a: array): Int + function loc_inv_1(loc: Ref): array + function loc_inv_2(loc: Ref): Int + + axiom { + (forall a: array, i: Int :: { array_loc(a, i) } loc_inv_1(array_loc(a, i)) == a && loc_inv_2(array_loc(a, i)) == i) + } + + axiom { + (forall a: array :: { alen(a) } alen(a) >= 0) + } +} + +field int: Int + +function aloc(a: array, i: Int): Ref + requires 0 <= i + requires i < alen(a) +{ + array_loc(a, i) +} + +method main(arr1: array, arr2: array) + requires alen(arr1) == 6 + requires alen(arr2) == 12 + requires (forall i: Int :: { aloc(arr1, i).int } (0 <= i && i < 6) ==> acc(aloc(arr1, i).int, 1 / 2)) + requires (forall i: Int :: { aloc(arr1, i).int } (0 <= i && i < 6) ==> aloc(arr1, i).int == 0) + + requires (forall i: Int :: { aloc(arr2, i).int } (0 <= i && i < 12) ==> acc(aloc(arr2, i).int, write)) + requires (forall i: Int :: { aloc(arr2, i).int } (0 <= i && i < 6) ==> aloc(arr2, i).int == 0 * (aloc(arr1, i).int - aloc(arr1, 0).int)) +{ + var x: Int := 0 + while (true) + invariant x == x + invariant (forall i: Int :: { aloc(arr2, i).int } (0 <= i && i < 6) ==> acc(aloc(arr2, i).int, write)) + invariant (forall i: Int :: { aloc(arr2, i).int } (6 <= i && i < 12) ==> acc(aloc(arr2, i).int, 1 / 2)) + invariant (forall i: Int :: { aloc(arr1, i).int } (0 <= i && i < 6) ==> acc(aloc(arr1, i).int, 1 / 2)) + { + assert aloc(arr2, 6).int == aloc(arr2, 6).int + //:: ExpectedOutput(assert.failed:assertion.false) + assert 3 + 8 == 38 + } +} \ No newline at end of file diff --git a/src/test/resources/all/issues/silicon/0678.vpr b/src/test/resources/all/issues/silicon/0678.vpr new file mode 100644 index 000000000..b803c53a0 --- /dev/null +++ b/src/test/resources/all/issues/silicon/0678.vpr @@ -0,0 +1,17 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +predicate P(x: Int) +field f: Int + +method mPred(r: Int) + requires P(r) +{ + assume exists x: Int :: { P(x) } perm(P(x)) > none +} + +method mField(r: Ref) + requires acc(r.f) +{ + assume exists x: Ref :: { x.f } perm(x.f) > none +} diff --git a/src/test/resources/all/issues/silver/0207.vpr b/src/test/resources/all/issues/silver/0207.vpr index 586562d26..bcd8be8ec 100644 --- a/src/test/resources/all/issues/silver/0207.vpr +++ b/src/test/resources/all/issues/silver/0207.vpr @@ -72,7 +72,7 @@ method getMin(t: BST) returns (min: Int) invariant !isNull(currentNode) invariant t == currentNode || value(currentNode) < value(t) invariant t != currentNode && !isNull(left(t)) ==> isParent(left(t), currentNode) - invariant forall pc: BST :: {value(pc), value(currentNode)} + invariant forall pc: BST :: {value(pc)} !isNull(pc) && isParent(pc, currentNode) ==> value(pc) >= value(currentNode) { currentNode := left(currentNode) diff --git a/src/test/resources/all/issues/silver/0586.vpr b/src/test/resources/all/issues/silver/0586.vpr index 0f7cb7f9f..31160d0e5 100644 --- a/src/test/resources/all/issues/silver/0586.vpr +++ b/src/test/resources/all/issues/silver/0586.vpr @@ -1,3 +1,6 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + field val: Int define NodeSeg(n) ( diff --git a/src/test/resources/all/issues/silver/0637.vpr b/src/test/resources/all/issues/silver/0637.vpr new file mode 100644 index 000000000..f0de7381d --- /dev/null +++ b/src/test/resources/all/issues/silver/0637.vpr @@ -0,0 +1,41 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +field f: Int + +method test1(xs: Seq[Ref]) + requires (forall i: Int, j: Int :: 0 <= i && i < |xs| && 0 <= j && j < |xs| ==> xs[i] == xs[j] ==> i == j) + //:: ExpectedOutput(not.wellformed:insufficient.permission) + requires (forall i: Int :: 0 <= i && i < |xs| ==> let j == (xs[i].f) in acc(xs[i].f, 1/2) && j == 0) +{ + +} + +method test2(x: Ref) + //:: ExpectedOutput(not.wellformed:insufficient.permission) + requires let j == (x.f) in (acc(x.f, 1/2) && j == 0) +{ + +} + +method test3(xs: Seq[Ref], ys: Seq[Int]) + requires |xs| == |ys| + requires (forall i: Int, j: Int :: 0 <= i && i < |xs| && 0 <= j && j < |xs| ==> xs[i] == xs[j] ==> i == j) + requires (forall i: Int :: 0 <= i && i < |xs| ==> let j == (ys[i]) in acc(xs[i].f, 1/2) && xs[i].f == j) +{ + assume |xs| > 3 + assert xs[2].f == ys[2] + //:: ExpectedOutput(assert.failed:assertion.false) + assert false +} + +method test4(xs: Seq[Ref], ys: Seq[Int]) + requires |xs| == |ys| + requires (forall i: Int, j: Int :: 0 <= i && i < |xs| && 0 <= j && j < |xs| ==> xs[i] == xs[j] ==> i == j) + requires (forall i: Int :: 0 <= i && i < |xs| ==> let j == (i) in acc(xs[j].f, 1/2) && xs[i].f == 0) +{ + assume |xs| > 3 + assert xs[2].f == 0 + //:: ExpectedOutput(assert.failed:assertion.false) + assert false +} \ No newline at end of file diff --git a/src/test/resources/all/issues/silver/0639.vpr b/src/test/resources/all/issues/silver/0639.vpr new file mode 100644 index 000000000..29eff0208 --- /dev/null +++ b/src/test/resources/all/issues/silver/0639.vpr @@ -0,0 +1,15 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +field f: Int + +method test(xs: Seq[Seq[Ref]]) + requires forall i: Int, j: Int, i0: Int, j0: Int :: + 0 <= i && i < |xs| && 0 <= j && j < |xs[i]| && + 0 <= i0 && i0 < |xs| && 0 <= j0 && j0 < |xs[i0]| && + (i != i0 || j != j0) + ==> xs[i][j] != xs[i0][j0] + requires forall i: Int :: 0 <= i && i < |xs| ==> + let xs_i == (xs[i]) in + forall j: Int :: 0 <= j && j < |xs_i| ==> + acc(xs[i][j].f, write) diff --git a/src/test/resources/quasihavoc/benchmarks/generate_benchmarks.py b/src/test/resources/quasihavoc/benchmarks/generate_benchmarks.py new file mode 100644 index 000000000..d6b78f763 --- /dev/null +++ b/src/test/resources/quasihavoc/benchmarks/generate_benchmarks.py @@ -0,0 +1,276 @@ +#! /usr/bin/python3 + +from pathlib import Path +import os + +script_file = Path(__file__).resolve() +# we are currently in silicon/src/test/resources/quasihavoc/benchmarks +top_dir = script_file.parent +os.chdir(top_dir) +os.makedirs('autogen', exist_ok=True) + +from string import Formatter +from collections import namedtuple + +arg = namedtuple("arg", ["name", "type"]) + +class testcase: + def __init__(self, prelude, methods): + self.prelude = prelude + self.methods = methods + + def write(self, fname, n): + havoc_filename = f"autogen/{fname}_havoc_{n}.vpr" + alt_filename = f"autogen/{fname}_alt_{n}.vpr" + + with open(havoc_filename, "w") as f: + f.write(self.prelude) + for m in self.methods: + f.write(m.write(True, n)) + + with open(alt_filename, "w") as f: + f.write(self.prelude) + for m in self.methods: + f.write(m.write(False, n)) + +class writer(): + def __init__(self): + self.output = "// !!! AUTOGENERATED !!!\n" + self.indent_level = 0 + + def write(self, s): + self.output += s + + def newline(self): + self.output += "\n" + + def indent(self): + self.indent_level += 1 + + def unindent(self): + self.indent_level -= 1 + + def write_line(self, s): + self.output += "\t"*self.indent_level + s + "\n" + +class method: + def __init__(self, args, requires, havoc_body, alt_body, name="foo"): + self.args = args + self.requires = requires + self.havoc_body = havoc_body + self.alt_body = alt_body + self.name = name + + def write(self, havoc, n): + + w = writer() + + method_name = f"{self.name}_{'havoc' if havoc else 'other'}" + w.write(f"method {method_name}(") + + # Write the arugments + str_args = [] + for arg in self.args: + str_arg_names = self.expand_fs(arg.name, n) + str_args += [f"{a}: {arg.type}" for a in str_arg_names] + + w.write(', '.join(str_args)) + w.write(')') + w.newline() + + # Write the requires + w.indent() + for require in self.requires: + for str_req in self.expand_fs(require, n): + w.write_line("requires " + str_req) + w.unindent() + + # Write the body + w.write_line("{") + w.indent() + + body = self.havoc_body if havoc else self.alt_body + for stmt in body: + for line in self.expand_fs(stmt, n): + w.write_line(line) + + w.unindent() + w.write_line("}") + + return w.output + + + def expand_fs(self, s, n): + # Check if it's a format string + if len([t for t in Formatter().parse(s) if t[1] is not None]) >= 1: + return [s.format(i) for i in range(n)] + else: + return [s] + +######################## +# alias_test.py + +prelude = """ +field f: Int +""" + +args = [arg("y", "Ref"), arg("z", "Ref"), arg("x{0}", "Ref")] +requires = [ + "acc(y.f) && y.f == 3", + "acc(z.f) && z.f == 4", + "x{0} == y || x{0} == z", +] + +common_body = [ + "//:: ExpectedOutput(assert.failed:assertion.false)", + "assert y.f == 3", +] + +body = ["quasihavoc x{0}.f"] + common_body +alt_body = ["exhale acc(x{0}.f); inhale acc(x{0}.f)"] + common_body + +alias_method = method(args, requires, body, alt_body) +alias_test = testcase(prelude, [alias_method]) + +########################## +# impl_test.py + +prelude = """ +field f: Int +""" + +args = [arg("y", "Ref"), arg("x{0}", "Ref")] +requires = ["acc(y.f) && y.f == 42"] + +common_body = [ + "assume y != x{0}", + "assert y.f == 42", +] + +havoc_body = ["quasihavoc y == x{0} ==> y.f"] + common_body +alt_body = ["exhale y == x{0} ==> acc(y.f); inhale y == x{0} ==> acc(y.f)"] + common_body + +impl_method = method(args, requires, havoc_body, alt_body) +impl_test = testcase(prelude, [impl_method]) + +####################### +# quasihavocall_test.py + +prelude = """ +field f: Int +""" + +args = [arg("x", "Ref"), arg("s", "Set[Ref]")] +requires = ["forall z: Ref :: z in s ==> acc(z.f) && z.f == 42", + "x in s"] + + +havoc_body = ["quasihavocall z: Ref :: z in s && z != x ==> z.f // {0}"] +alt_body = ["exhale forall z: Ref :: z in s && z != x ==> acc(z.f); inhale forall z: Ref :: z in s && z != x ==> acc(z.f) // {0}"] + +common_body = [ + "assert x.f == 42" +] + +havocall_method = method(args, requires, havoc_body + common_body, alt_body + common_body) +havocall_test = testcase(prelude, [havocall_method]) + + +######################## +# havocall_no_cond.py + +prelude = """ +field f: Int +field g: Int +method ___silicon_hack407_havoc_all_Pred() + +predicate Pred(x: Ref) { + acc(x.f) && x.f >= 0 +} +""" + +args = [arg("x", "Ref")] + +requires = ["Pred(x)"] + +havoc_body = ["quasihavocall z: Ref :: Pred(z) // {0}"] +alt_body = ["___silicon_hack407_havoc_all_Pred() // {0}"] + +havocall_no_cond_method = method(args, requires, havoc_body, alt_body) +havocall_no_cond_test = testcase(prelude, [havocall_no_cond_method]) + +######################### +# havocall_sets.py + +prelude = """ +field f: Int +""" + +args = [arg("x", "Ref"), arg("s{0}", "Set[Ref]"), arg("t", "Set[Ref]")] + +requires = ["forall z: Ref :: z in s{0} ==> acc(z.f)", + "forall z: Ref :: z in t ==> acc(z.f)", + "x in s{0} ==> x.f == 42", + "x in t ==> x.f == 43", +] + +havoc_body = ["quasihavocall z: Ref :: z in s{0} ==> z.f"] +alt_body = ["exhale forall z: Ref :: z in s{0} ==> acc(z.f); inhale forall z: Ref :: z in s{0} ==> acc(z.f)"] + +common_body = ["assume !(x in s{0})", + "assume x in t", + "assert x.f == 43"] + + +havocall_sets_method = method(args, requires, havoc_body + common_body, alt_body + common_body) +havocall_sets_test = testcase(prelude, [havocall_sets_method]) + +######################### +# havoc fractions + +prelude = """ +field f: Int +""" + +args = [arg("y", "Ref"), arg("x{0}", "Ref"), arg("z", "Ref"), arg("pz", "Perm")] + +requires = [ + "acc(x{0}.f, 1/1000)", + "acc(z.f, 1/1000) && z.f == 1234", +] + +havoc_body = ["quasihavoc y.f"] +alt_body = ["label L; var py: Perm := perm(y.f); exhale acc(y.f, py); inhale acc(y.f, py)"] + +common_body = [ + "assume y == x{0}", + "assume y == z", + "//:: ExpectedOutput(assert.failed:assertion.false)", + "assert z.f == 1234 // should fail", +] + +havoc_fracs_method = method( + args, + requires, + havoc_body + common_body, + alt_body + common_body +) +havoc_fracs_test = testcase(prelude, [havoc_fracs_method]) + + +######################## +# Output the tests + +for i in range(3, 11): + alias_test.write("alias_test", i) + impl_test.write("impl_test", i) + +for i in range(10, 100, 10): + havocall_test.write("havocall_test", i) + havocall_no_cond_test.write("no_cond", i) + +for i in range(10, 20): + havocall_sets_test.write("sets_test", i) + +for i in range(3, 16): + havoc_fracs_test.write("havoc_fracs", i) From 3195a8df3bdb40ab09f05ddafd2beb1cab4ea170 Mon Sep 17 00:00:00 2001 From: Linard Arquint Date: Tue, 27 Feb 2024 17:57:55 +0100 Subject: [PATCH 10/41] adds license headers --- .../plugin/standard/reasoning/BeforeVerifyHelper.scala | 6 ++++++ .../plugin/standard/reasoning/ReasoningASTExtension.scala | 6 ++++++ .../silver/plugin/standard/reasoning/ReasoningErrors.scala | 2 +- .../plugin/standard/reasoning/ReasoningPASTExtension.scala | 6 ++++++ .../silver/plugin/standard/reasoning/ReasoningPlugin.scala | 7 ++++++- .../standard/reasoning/analysis/SetGraphComparison.scala | 6 ++++++ .../standard/reasoning/analysis/VarAnalysisGraph.scala | 6 ++++++ .../standard/reasoning/analysis/VarAnalysisSet.scala | 6 ++++++ src/test/resources/reasoning/existential_elim.vpr | 3 +++ src/test/resources/reasoning/immutableVar.vpr | 3 +++ src/test/resources/reasoning/influenced_heap.vpr | 3 +++ src/test/resources/reasoning/old_call.vpr | 3 +++ src/test/resources/reasoning/set_vs_graph.vpr | 3 +++ src/test/resources/reasoning/test.vpr | 3 +++ src/test/resources/reasoning/test2.vpr | 3 +++ src/test/resources/reasoning/test3.vpr | 3 +++ src/test/resources/reasoning/universal_intro.vpr | 3 +++ 17 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala index 32cadaa8b..60f4ab934 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2024 ETH Zurich. + package viper.silver.plugin.standard.reasoning import org.jgrapht.graph.DefaultEdge diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala index 2f73d1061..7cfb6cbc3 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2024 ETH Zurich. + package viper.silver.plugin.standard.reasoning import viper.silver.ast._ diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala index 0442ede0f..04088775e 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // -// Copyright (c) 2011-2022 ETH Zurich. +// Copyright (c) 2011-2024 ETH Zurich. package viper.silver.plugin.standard.reasoning diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala index 949a60f2e..c63a54cf9 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2024 ETH Zurich. + package viper.silver.plugin.standard.reasoning import viper.silver.ast.{ExtensionExp, Label, LocalVar, LocalVarDecl, Position, Seqn, Stmt, Trigger} diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index 25a2424ff..1ece95b2f 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -1,5 +1,10 @@ -package viper.silver.plugin.standard.reasoning +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2024 ETH Zurich. +package viper.silver.plugin.standard.reasoning import fastparse._ import org.jgrapht.Graph diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala index 63567c984..dc2baf1aa 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2024 ETH Zurich. + package viper.silver.plugin.standard.reasoning.analysis import org.jgrapht.Graph diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala index 5e4f24427..ab891f6bf 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2024 ETH Zurich. + package viper.silver.plugin.standard.reasoning.analysis import org.jgrapht.Graph diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala index edfdfa05d..7a8006822 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2024 ETH Zurich. + package viper.silver.plugin.standard.reasoning.analysis import viper.silver.ast.{Assume, BinExp, Declaration, Exp, FieldAssign, If, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Seqn, Stmt, UnExp, While} diff --git a/src/test/resources/reasoning/existential_elim.vpr b/src/test/resources/reasoning/existential_elim.vpr index 53fc84f64..25e2ddeae 100644 --- a/src/test/resources/reasoning/existential_elim.vpr +++ b/src/test/resources/reasoning/existential_elim.vpr @@ -1,3 +1,6 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + function eq(x: Int, y: Int): Bool { x == y } diff --git a/src/test/resources/reasoning/immutableVar.vpr b/src/test/resources/reasoning/immutableVar.vpr index 67556dc54..38fa6f70f 100644 --- a/src/test/resources/reasoning/immutableVar.vpr +++ b/src/test/resources/reasoning/immutableVar.vpr @@ -1,3 +1,6 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + function P(x: Int) : Bool { x == 0 } diff --git a/src/test/resources/reasoning/influenced_heap.vpr b/src/test/resources/reasoning/influenced_heap.vpr index bf120b2a4..a207ad563 100644 --- a/src/test/resources/reasoning/influenced_heap.vpr +++ b/src/test/resources/reasoning/influenced_heap.vpr @@ -1,3 +1,6 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + field f:Int diff --git a/src/test/resources/reasoning/old_call.vpr b/src/test/resources/reasoning/old_call.vpr index 55246600e..7ce7389d2 100644 --- a/src/test/resources/reasoning/old_call.vpr +++ b/src/test/resources/reasoning/old_call.vpr @@ -1,3 +1,6 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + method foo() //:: ExpectedOutput(typechecker.error) requires oldCall[l](lemma()) diff --git a/src/test/resources/reasoning/set_vs_graph.vpr b/src/test/resources/reasoning/set_vs_graph.vpr index 1b1bd6bcc..4c905c149 100644 --- a/src/test/resources/reasoning/set_vs_graph.vpr +++ b/src/test/resources/reasoning/set_vs_graph.vpr @@ -1,3 +1,6 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + function P(x: Int) : Bool { x == 0 } diff --git a/src/test/resources/reasoning/test.vpr b/src/test/resources/reasoning/test.vpr index e07d52690..324567bf9 100644 --- a/src/test/resources/reasoning/test.vpr +++ b/src/test/resources/reasoning/test.vpr @@ -1,3 +1,6 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + function P(x: Int) : Bool { x == 0 } diff --git a/src/test/resources/reasoning/test2.vpr b/src/test/resources/reasoning/test2.vpr index b4813bfd2..19be7dfb7 100644 --- a/src/test/resources/reasoning/test2.vpr +++ b/src/test/resources/reasoning/test2.vpr @@ -1,3 +1,6 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + field f: Int // Should be accepted diff --git a/src/test/resources/reasoning/test3.vpr b/src/test/resources/reasoning/test3.vpr index 4511f7767..e050b8e21 100644 --- a/src/test/resources/reasoning/test3.vpr +++ b/src/test/resources/reasoning/test3.vpr @@ -1,3 +1,6 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + function P(x: Int) : Bool { x == 0 } diff --git a/src/test/resources/reasoning/universal_intro.vpr b/src/test/resources/reasoning/universal_intro.vpr index 2835d23ef..88e288b88 100644 --- a/src/test/resources/reasoning/universal_intro.vpr +++ b/src/test/resources/reasoning/universal_intro.vpr @@ -1,3 +1,6 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + function greaterthanzero(x:Int) :Bool { x > 0 From 0c0efd70fe3cc8571042a8313ca3880ef27dfef9 Mon Sep 17 00:00:00 2001 From: Linard Arquint Date: Tue, 27 Feb 2024 18:01:37 +0100 Subject: [PATCH 11/41] fixes a compiler error --- src/main/scala/viper/silver/frontend/SilFrontend.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/scala/viper/silver/frontend/SilFrontend.scala b/src/main/scala/viper/silver/frontend/SilFrontend.scala index 82cb8ec2b..fa720e914 100644 --- a/src/main/scala/viper/silver/frontend/SilFrontend.scala +++ b/src/main/scala/viper/silver/frontend/SilFrontend.scala @@ -261,9 +261,7 @@ trait SilFrontend extends DefaultFrontend { if (state == DefaultStates.ConsistencyCheck && _errors.isEmpty) { filter(_program.get) match { - case Succ(program) => - _program = Some(program) - reporter report PluginTransformationsAppliedMessage(program) + case Succ(program) => _program = Some(program) case Fail(errors) => _errors ++= errors } } From 3da1caf2a30ed7236d0e8df197837968ad40b015 Mon Sep 17 00:00:00 2001 From: Linard Arquint Date: Tue, 27 Feb 2024 18:17:04 +0100 Subject: [PATCH 12/41] changes testcases that use 'heap', which is now a keyword --- .../resources/all/basic/many_conjuncts.vpr | 34 +++++++++---------- .../resources/all/issues/silicon/0365.vpr | 6 ++-- .../quantifiedpermissions/misc/misc1.vpr | 16 ++++----- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/test/resources/all/basic/many_conjuncts.vpr b/src/test/resources/all/basic/many_conjuncts.vpr index 3fde77a99..9951e9cc0 100644 --- a/src/test/resources/all/basic/many_conjuncts.vpr +++ b/src/test/resources/all/basic/many_conjuncts.vpr @@ -15,7 +15,7 @@ field acq: Bool predicate AcqConjunct(x: Ref, idx: Int) domain parallelHeaps { - function heap(x: Ref) : Int + function heap_lookup(x: Ref) : Int function is_ghost(x:Ref) : Bool } @@ -28,25 +28,25 @@ domain reads { method read(data: Ref, count: Ref, ghost: Ref) returns (v: Int) - requires heap(data) == 0 && (heap(count) == 0 && (heap(ghost) == 0 && (is_ghost(ghost) && (acc(data.val, rd()) && (acc(ghost.val, rd()) && (acc(count.acq, wildcard) && count.acq == false && acc(AcqConjunct(count, 0), wildcard) && (acc(count.rel, wildcard) && count.rel == 0 && acc(count.init, wildcard)))))))) - ensures heap(data) == 0 && (heap(count) == 0 && (heap(ghost) == 0 && (is_ghost(ghost) && (acc(data.val, rd()) && (acc(ghost.val, rd()) && (acc(count.acq, wildcard) && count.acq == false && acc(AcqConjunct(count, 0), wildcard) && (acc(count.rel, wildcard) && count.rel == 0 && acc(count.init, wildcard)))))))) && (data.val == v && [true, perm(data.val) == none]) + requires heap_lookup(data) == 0 && (heap_lookup(count) == 0 && (heap_lookup(ghost) == 0 && (is_ghost(ghost) && (acc(data.val, rd()) && (acc(ghost.val, rd()) && (acc(count.acq, wildcard) && count.acq == false && acc(AcqConjunct(count, 0), wildcard) && (acc(count.rel, wildcard) && count.rel == 0 && acc(count.init, wildcard)))))))) + ensures heap_lookup(data) == 0 && (heap_lookup(count) == 0 && (heap_lookup(ghost) == 0 && (is_ghost(ghost) && (acc(data.val, rd()) && (acc(ghost.val, rd()) && (acc(count.acq, wildcard) && count.acq == false && acc(AcqConjunct(count, 0), wildcard) && (acc(count.rel, wildcard) && count.rel == 0 && acc(count.init, wildcard)))))))) && (data.val == v && [true, perm(data.val) == none]) { v := data.val } method read_erroneous(data: Ref, count: Ref, ghost: Ref) returns (v: Int) - requires heap(data) == 0 && (heap(count) == 0 && (heap(ghost) == 0 && (is_ghost(ghost) && (acc(data.val, rd()) && (acc(ghost.val, rd()) && (acc(count.acq, wildcard) && count.acq == false && acc(AcqConjunct(count, 0), wildcard) && (acc(count.rel, wildcard) && count.rel == 0 && acc(count.init, wildcard)))))))) + requires heap_lookup(data) == 0 && (heap_lookup(count) == 0 && (heap_lookup(ghost) == 0 && (is_ghost(ghost) && (acc(data.val, rd()) && (acc(ghost.val, rd()) && (acc(count.acq, wildcard) && count.acq == false && acc(AcqConjunct(count, 0), wildcard) && (acc(count.rel, wildcard) && count.rel == 0 && acc(count.init, wildcard)))))))) //:: ExpectedOutput(postcondition.violated:assertion.false) - ensures heap(data) == 0 && (heap(count) == 0 && (heap(ghost) == 0 && (is_ghost(ghost) && (acc(data.val, rd()) && (acc(ghost.val, rd()) && (acc(count.acq, wildcard) && count.acq == false && acc(AcqConjunct(count, 0), wildcard) && (acc(count.rel, wildcard) && count.rel == 0 && acc(count.init, wildcard)))))))) && (data.val == v && [true, perm(data.val) == none] && false) + ensures heap_lookup(data) == 0 && (heap_lookup(count) == 0 && (heap_lookup(ghost) == 0 && (is_ghost(ghost) && (acc(data.val, rd()) && (acc(ghost.val, rd()) && (acc(count.acq, wildcard) && count.acq == false && acc(AcqConjunct(count, 0), wildcard) && (acc(count.rel, wildcard) && count.rel == 0 && acc(count.init, wildcard)))))))) && (data.val == v && [true, perm(data.val) == none] && false) { v := data.val } method read2(data: Ref, count: Ref, ghost: Ref) returns (v: Int) requires - heap(data) == 0 && - heap(count) == 0 && - heap(ghost) == 0 && + heap_lookup(data) == 0 && + heap_lookup(count) == 0 && + heap_lookup(ghost) == 0 && is_ghost(ghost) && acc(data.val, rd()) && acc(ghost.val, rd()) && @@ -58,9 +58,9 @@ method read2(data: Ref, count: Ref, ghost: Ref) returns (v: Int) acc(count.init, wildcard) ensures - heap(data) == 0 && - heap(count) == 0 && - heap(ghost) == 0 && + heap_lookup(data) == 0 && + heap_lookup(count) == 0 && + heap_lookup(ghost) == 0 && is_ghost(ghost) && acc(data.val, rd()) && acc(ghost.val, rd()) && @@ -78,9 +78,9 @@ method read2(data: Ref, count: Ref, ghost: Ref) returns (v: Int) method read2_erroneous(data: Ref, count: Ref, ghost: Ref) returns (v: Int) requires - heap(data) == 0 && - heap(count) == 0 && - heap(ghost) == 0 && + heap_lookup(data) == 0 && + heap_lookup(count) == 0 && + heap_lookup(ghost) == 0 && is_ghost(ghost) && acc(data.val, rd()) && acc(ghost.val, rd()) && @@ -93,9 +93,9 @@ method read2_erroneous(data: Ref, count: Ref, ghost: Ref) returns (v: Int) ensures //:: ExpectedOutput(postcondition.violated:assertion.false) - heap(data) == 0 && - heap(count) == 0 && - heap(ghost) == 0 && + heap_lookup(data) == 0 && + heap_lookup(count) == 0 && + heap_lookup(ghost) == 0 && is_ghost(ghost) && acc(data.val, rd()) && acc(ghost.val, rd()) && diff --git a/src/test/resources/all/issues/silicon/0365.vpr b/src/test/resources/all/issues/silicon/0365.vpr index 9a086ced6..5800688ec 100644 --- a/src/test/resources/all/issues/silicon/0365.vpr +++ b/src/test/resources/all/issues/silicon/0365.vpr @@ -16,15 +16,15 @@ function tokCountRef(r:Ref): Ref domain parallelHeaps { function temp(x: Ref): Ref function temp_inv(x: Ref): Ref - function heap(x: Ref): Int + function heap_lookup(x: Ref): Int function is_ghost(x: Ref): Bool // WARNING: The two axioms can cause a matching loop axiom inv_temp { - (forall r: Ref :: { temp(r) } temp_inv(temp(r)) == r && (is_ghost(r) ? temp(r) == r : heap(temp(r)) == heap(r) - 3)) + (forall r: Ref :: { temp(r) } temp_inv(temp(r)) == r && (is_ghost(r) ? temp(r) == r : heap_lookup(temp(r)) == heap_lookup(r) - 3)) } axiom inv_temp_inv { - (forall r: Ref :: { temp_inv(r) } temp(temp_inv(r)) == r && (is_ghost(r) ? temp_inv(r) == r : heap(temp_inv(r)) == heap(r) + 3)) + (forall r: Ref :: { temp_inv(r) } temp(temp_inv(r)) == r && (is_ghost(r) ? temp_inv(r) == r : heap_lookup(temp_inv(r)) == heap_lookup(r) + 3)) } } diff --git a/src/test/resources/quantifiedpermissions/misc/misc1.vpr b/src/test/resources/quantifiedpermissions/misc/misc1.vpr index d30d18644..fff21de5e 100644 --- a/src/test/resources/quantifiedpermissions/misc/misc1.vpr +++ b/src/test/resources/quantifiedpermissions/misc/misc1.vpr @@ -1,6 +1,6 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/publicdomain/zero/1.0/ - +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + field val : Int domain parallelHeaps { @@ -12,10 +12,10 @@ domain parallelHeaps { function temp(x: Ref) : Ref function temp_inv(x: Ref) : Ref - function heap(x: Ref) : Int + function heap_lookup(x: Ref) : Int function is_ghost(x:Ref) : Bool - axiom inv_temp { forall r:Ref :: {temp(r)} temp_inv(temp(r)) == r && (is_ghost(r) ? temp(r) == r : heap(temp(r)) == -3) } + axiom inv_temp { forall r:Ref :: {temp(r)} temp_inv(temp(r)) == r && (is_ghost(r) ? temp(r) == r : heap_lookup(temp(r)) == -3) } } domain reads { @@ -31,9 +31,9 @@ domain reads { method clone(data: Ref, count: Ref, ghost: Ref) - requires heap(data) == 0 - && heap(count) == 0 - && heap(ghost) == 0 + requires heap_lookup(data) == 0 + && heap_lookup(count) == 0 + && heap_lookup(ghost) == 0 && is_ghost(ghost) && acc(data.val, rd()) && acc(ghost.val, rd()) From c2175656baa9dd56d501031164365bd90f6b3bad Mon Sep 17 00:00:00 2001 From: Linard Arquint Date: Wed, 28 Feb 2024 14:37:01 +0100 Subject: [PATCH 13/41] cleans up reasoning plugin further --- .../reasoning/BeforeVerifyHelper.scala | 30 ++++---- .../reasoning/ReasoningASTExtension.scala | 74 +++++++------------ .../reasoning/ReasoningPASTExtension.scala | 41 ++++++---- .../standard/reasoning/ReasoningPlugin.scala | 29 +++----- .../analysis/SetGraphComparison.scala | 4 +- .../reasoning/analysis/VarAnalysisGraph.scala | 4 +- .../reasoning/analysis/VarAnalysisSet.scala | 3 +- 7 files changed, 87 insertions(+), 98 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala index 60f4ab934..08050f7cd 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala @@ -17,9 +17,6 @@ import scala.collection.mutable import scala.jdk.CollectionConverters.CollectionHasAsScala trait BeforeVerifyHelper { - - - /** methods to rename variables for the encoding of the new syntax */ def uniqueName(name: String, usedNames: mutable.Set[String]): String = { var i = 1 @@ -46,8 +43,7 @@ trait BeforeVerifyHelper { def applySubstitutionWithExp[E <: Exp](mapping: Seq[(LocalVarDecl, Exp)], exp: E): E = { Expressions.instantiateVariables(exp, mapping.map(_._1.localVar), mapping.map(_._2)) } - - + /** * get all variables that are assigned to inside the block and take intersection with universal introduction * variables. If they are contained throw error since these variables should be immutable @@ -68,12 +64,15 @@ trait BeforeVerifyHelper { } } - + private def specifiesLemma(m: Method): Boolean = (m.pres ++ m.posts).exists { + case _: Lemma => true + case _ => false + } + /** check if isLemma precondition is correct */ - def checkLemma(input: Program, reportError: AbstractError => Unit) = { + def checkLemma(input: Program, reportError: AbstractError => Unit): Unit = { input.methods.foreach(method => { - var containsLemma: Boolean = method.pres.exists(p => p.isInstanceOf[Lemma]) - containsLemma = (containsLemma || method.posts.exists(p => p.isInstanceOf[Lemma])) + val containsLemma = specifiesLemma(method) var containsDecreases = false if (containsLemma) { /** check preconditions for decreases clause */ @@ -142,8 +141,7 @@ trait BeforeVerifyHelper { false case m@MethodCall(methodName, _, _) => val mc = prog.findMethod(methodName) - var containsLemma: Boolean = mc.pres.exists(p => p.isInstanceOf[Lemma]) - containsLemma = containsLemma || mc.posts.exists(p => p.isInstanceOf[Lemma]) + val containsLemma = specifiesLemma(mc) /** if called method is not a lemma report error */ if (!containsLemma) { @@ -182,7 +180,10 @@ trait BeforeVerifyHelper { influenced_exists = true /** create target variable of flowannotation based on whether it is the heap or another return variable */ - val target_var: LocalVar = if (target.isInstanceOf[Var]) target.asInstanceOf[Var].var_decl.asInstanceOf[LocalVar] else body_graph_analysis.heap_vertex.localVar + val target_var: LocalVar = target match { + case value: Var => value.decl + case _ => body_graph_analysis.heap_vertex.localVar + } val target_decl: LocalVarDecl = LocalVarDecl(target_var.name, target_var.typ)(v.pos) /** check whether the target variable is in fact a return variable */ @@ -197,7 +198,10 @@ trait BeforeVerifyHelper { args.foreach(arg => { /** decide for each variable in the set of variables of the flow annotation whether they represent a normal variable or the heap */ - val arg_var: LocalVar = if (arg.isInstanceOf[Var]) arg.asInstanceOf[Var].var_decl.asInstanceOf[LocalVar] else body_graph_analysis.heap_vertex.localVar + val arg_var: LocalVar = arg match { + case value: Var => value.decl + case _ => body_graph_analysis.heap_vertex.localVar + } val arg_decl: LocalVarDecl = LocalVarDecl(arg_var.name, arg_var.typ)(arg_var.pos) /** check that each variable in the set is a method argument */ diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala index 7cfb6cbc3..166e8d6a8 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala @@ -9,7 +9,7 @@ package viper.silver.plugin.standard.reasoning import viper.silver.ast._ import viper.silver.ast.pretty.FastPrettyPrinter.{ContOps, brackets, char, defaultIndent, group, line, nest, nil, parens, show, showBlock, showVars, space, ssep, text, toParenDoc} import viper.silver.ast.pretty.PrettyPrintPrimitives -import viper.silver.ast.utility.{Consistency} +import viper.silver.ast.utility.Consistency import viper.silver.verifier.{ConsistencyError, Failure, VerificationResult} @@ -28,11 +28,8 @@ case class ExistentialElim(varList: Seq[LocalVarDecl], trigs: Seq[Trigger], exp: override val extensionSubnodes: Seq[Node] = varList ++ trigs ++ Seq(exp) - /** declarations contributed by this statement that should be added to the parent scope */ override def declarationsInParentScope: Seq[Declaration] = varList - - } case class UniversalIntro(varList: Seq[LocalVarDecl], triggers: Seq[Trigger], exp1: Exp, exp2: Exp, block: Seqn)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt with Scope { @@ -42,9 +39,7 @@ case class UniversalIntro(varList: Seq[LocalVarDecl], triggers: Seq[Trigger], ex (if (varList.isEmpty) Seq(ConsistencyError("Quantifier must have at least one quantified variable.", pos)) else Seq()) ++ Consistency.checkAllVarsMentionedInTriggers(varList, triggers) - - override val scopedDecls = varList - + override val scopedDecls: Seq[Declaration] = varList override lazy val prettyPrint: PrettyPrintPrimitives#Cont = { text("prove forall") <+> showVars(varList) <+> @@ -54,58 +49,63 @@ case class UniversalIntro(varList: Seq[LocalVarDecl], triggers: Seq[Trigger], ex showBlock(block) } - override val extensionSubnodes: Seq[Node] = varList ++ triggers ++ Seq(exp1) ++ Seq(exp2) ++ Seq(block) + override val extensionSubnodes: Seq[Node] = varList ++ triggers ++ Seq(exp1, exp2, block) } -sealed trait FlowVar extends Node { +sealed trait FlowVar extends ExtensionExp { + override def extensionIsPure: Boolean = true + override def verifyExtExp(): VerificationResult = { + assert(assertion = false, "FlowAnalysis: verifyExtExp has not been implemented.") + Failure(Seq(ConsistencyError("FlowAnalysis: verifyExtExp has not been implemented.", pos))) + } } case class Heap()(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowVar { - + override val extensionSubnodes: Seq[Node] = Seq.empty + override def typ: Type = InternalType + override def prettyPrint: PrettyPrintPrimitives#Cont = PHeapKeyword.keyword } -case class Var(decl:Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowVar { - def var_decl: Exp = decl +case class Var(decl: LocalVar)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowVar { + override val extensionSubnodes: Seq[Node] = Seq(decl) + override def typ: Type = decl.typ + override def prettyPrint: PrettyPrintPrimitives#Cont = show(decl) } + case class FlowAnnotation(v: FlowVar, varList: Seq[FlowVar])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionExp with Node with Scope { override def extensionIsPure: Boolean = true - override val scopedDecls = Seq() + override val scopedDecls: Seq[Declaration] = Seq() override def typ: Type = Bool override def verifyExtExp(): VerificationResult = { assert(assertion = false, "FlowAnalysis: verifyExtExp has not been implemented.") Failure(Seq(ConsistencyError("FlowAnalysis: verifyExtExp has not been implemented.", pos))) - } - override def extensionSubnodes: Seq[Node] = { - if (v.isInstanceOf[Var]) (Seq(v.asInstanceOf[Var].var_decl)) else (Seq()) ++ varList.filter(vl => vl.isInstanceOf[Var]).map(vl => vl.asInstanceOf[Var].var_decl) - } + override def extensionSubnodes: Seq[Node] = Seq(v) ++ varList /** Pretty printing functionality as defined for other nodes in class FastPrettyPrinter. * Sample implementation would be text("old") <> parens(show(e)) for pretty-printing an old-expression. */ override def prettyPrint: PrettyPrintPrimitives#Cont = { text("influenced") <+> (v match { - case value: Var => (show(value.var_decl)) + case value: Var => (show(value.decl)) case _ => text("heap") }) <+> text("by") <+> ssep(varList.map { - case value: Var => show(value.var_decl) + case value: Var => show(value.decl) case _ => text("heap") }, group(char(',') <> line(" "))) } } - case class Lemma()(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionExp with Node with Scope { - override def extensionIsPure: Boolean = true - override val scopedDecls = Seq() + override val scopedDecls: Seq[Declaration] = Seq() override def typ: Type = Bool @@ -120,12 +120,11 @@ case class Lemma()(val pos: Position = NoPosition, val info: Info = NoInfo, val * Sample implementation would be text("old") <> parens(show(e)) for pretty-printing an old-expression. */ override def prettyPrint: PrettyPrintPrimitives#Cont = { text("isLemma") - } } -case class OldCall(methodName: String, args: Seq[Exp], rets: Seq[LocalVar], l:Label)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt with Scope { - override val scopedDecls = Seq() +case class OldCall(methodName: String, args: Seq[Exp], rets: Seq[LocalVar], oldLabel: String)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt with Scope { + override val scopedDecls: Seq[Declaration] = Seq() override lazy val check: Seq[ConsistencyError] = { var s = Seq.empty[ConsistencyError] @@ -137,9 +136,8 @@ case class OldCall(methodName: String, args: Seq[Exp], rets: Seq[LocalVar], l:La s } - override lazy val prettyPrint: PrettyPrintPrimitives#Cont = { - val call = text("oldCall") <> brackets(show(l)) <> text(methodName) <> nest(defaultIndent, parens(ssep(args map show, group(char(',') <> line)))) + val call = text("oldCall") <> brackets(text(oldLabel)) <> text(methodName) <> nest(defaultIndent, parens(ssep(args map show, group(char(',') <> line)))) rets match { case Nil => call case _ => ssep(rets map show, char(',') <> space) <+> ":=" <+> call @@ -148,25 +146,3 @@ case class OldCall(methodName: String, args: Seq[Exp], rets: Seq[LocalVar], l:La override val extensionSubnodes: Seq[Node] = args ++ rets } -/* -case class OldCall(methodName: String, args: Seq[Exp], l:Label)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionExp { - override lazy val check: Seq[ConsistencyError] = { - var s = Seq.empty[ConsistencyError] - if (!Consistency.noResult(this)) - s :+= ConsistencyError("Result variables are only allowed in postconditions of functions.", pos) - s ++= args.flatMap(Consistency.checkPure) - s - } - - override lazy val prettyPrint: PrettyPrintPrimitives#Cont = - text("oldCall") <> brackets(show(l)) <> text(methodName) <> nest(defaultIndent, parens(ssep(args map show, group(char(',') <> line)))) - - override val extensionSubnodes: Seq[Node] = args - - override def extensionIsPure: Boolean = true - - override def typ: Type = ??? - - override def verifyExtExp(): VerificationResult = ??? -} - */ \ No newline at end of file diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala index c63a54cf9..cb1553576 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -6,9 +6,10 @@ package viper.silver.plugin.standard.reasoning -import viper.silver.ast.{ExtensionExp, Label, LocalVar, LocalVarDecl, Position, Seqn, Stmt, Trigger} +import viper.silver.FastMessaging +import viper.silver.ast.{ExtensionExp, LocalVar, LocalVarDecl, Position, Seqn, Stmt, Trigger} import viper.silver.parser.TypeHelper.Bool -import viper.silver.parser.{NameAnalyser, PAssignTarget, PCall, PCallable, PDelimited, PExp, PExtender, PGrouped, PIdnRef, PKeywordLang, PKeywordStmt, PKw, PLabel, PLocalVarDecl, PNode, PReserved, PSeqn, PStmt, PSym, PSymOp, PTrigger, PType, PTypeSubstitution, Translator, TypeChecker} +import viper.silver.parser.{NameAnalyser, PAssignTarget, PCall, PCallable, PDelimited, PExp, PExtender, PGrouped, PIdnRef, PIdnUseExp, PKeywordLang, PKeywordStmt, PKw, PLabel, PLocalVarDecl, PReserved, PSeqn, PStmt, PSym, PSymOp, PTrigger, PType, PTypeSubstitution, Translator, TypeChecker} case object PObtainKeyword extends PKw("obtain") with PKeywordLang with PKeywordStmt case object PWhereKeyword extends PKw("where") with PKeywordLang @@ -18,7 +19,7 @@ case object PImpliesKeyword extends PKw("implies") with PKeywordLang case object PInfluencedKeyword extends PKw("influenced") with PKeywordLang with PKw.PostSpec case object PByKeyword extends PKw("by") with PKeywordLang case object PHeapKeyword extends PKw("heap") with PKeywordLang -case object PIsLemmaKeyword extends PKw("isLemma") with PKeywordLang with PKw.MethodSpec +case object PIsLemmaKeyword extends PKw("isLemma") with PKeywordLang with PKw.PreSpec with PKw.PostSpec case object POldCallKeyword extends PKw("oldCall") with PKeywordLang with PKeywordStmt case class PExistentialElim(obtainKw: PReserved[PObtainKeyword.type], delimitedVarDecls: PDelimited[PLocalVarDecl, PSym.Comma], whereKw: PReserved[PWhereKeyword.type], trig: Seq[PTrigger], e: PExp)(val pos: (Position, Position)) extends PExtender with PStmt { @@ -90,21 +91,30 @@ case class PFlowAnnotation(v: PFlowVar, byKw: PReserved[PByKeyword.type], groupe } } -sealed trait PFlowVar extends PNode { - def translate(t:Translator): FlowVar +sealed trait PFlowVar extends PExtender with PExp { + override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = typecheck(t, n) + + override def typecheck(t: TypeChecker, n: NameAnalyser): Option[Seq[String]] = None + + def translate(t: Translator): FlowVar + + override def typeSubstitutions: Seq[PTypeSubstitution] = Seq(PTypeSubstitution.id) + + override def forceSubstitution(ts: PTypeSubstitution): Unit = {} } case class PHeap(heap: PReserved[PHeapKeyword.type])(val pos: (Position,Position)) extends PFlowVar { - override def translate(t:Translator): Heap = { + override def translate(t: Translator): Heap = { Heap()(t.liftPos(this)) } override def pretty: String = PHeapKeyword.keyword } -case class PVar(decl:PExp)(val pos: (Position,Position)) extends PFlowVar { - override def translate(t:Translator): Var = { - Var(t.exp(decl))(t.liftPos(this)) +case class PVar(decl: PIdnUseExp)(val pos: (Position,Position)) extends PFlowVar { + override def translate(t: Translator): Var = { + // due to the implementation of `t.exp`, a LocalVar should be returned + Var(t.exp(decl).asInstanceOf[LocalVar])(t.liftPos(this)) } override def pretty: String = decl.pretty @@ -115,9 +125,9 @@ case class PLemmaClause()(val pos: (Position,Position)) extends PExtender with P override def forceSubstitution(ts: PTypeSubstitution): Unit = {} - override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = { - None - } + override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = typecheck(t, n) + + override def typecheck(t: TypeChecker, n: NameAnalyser): Option[Seq[String]] = None override def translateExp(t: Translator): ExtensionExp = { Lemma()(t.liftPos(this)) @@ -135,12 +145,17 @@ case class POldCall(lhs: PDelimited[PExp with PAssignTarget, PSym.Comma], op: Op ) targets.foreach(r => t.checkTopTyped(r, None)) + targets filter { + case _: PIdnUseExp => false + case _ => true + } foreach(target => t.messages ++= FastMessaging.message(target, s"expected an identifier but got $target")) None } override def translateStmt(t: Translator): Stmt = { val labelName = lbl.inner.fold(_.rs.keyword, _.name) - OldCall(idnref.name, args map t.exp, (targets map t.exp).asInstanceOf[Seq[LocalVar]], Label(labelName, Seq())(t.liftPos(lbl)))(t.liftPos(this)) + val translatedTargets = targets.map(target => t.exp(target).asInstanceOf[LocalVar]) // due to the typecheck and the implementation of `t.exp`, a LocalVar should be returned + OldCall(idnref.name, args map t.exp, translatedTargets, labelName)(t.liftPos(this)) } } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index 1ece95b2f..0df3b9572 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -50,7 +50,7 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, def heap[$: P]: P[PHeap] = P(P(PHeapKeyword) map (PHeap(_) _)).pos // note that the parentheses are not redundant - def singleVar[$: P]: P[PVar] = P(exp map (PVar(_) _)).pos // note that the parentheses are not redundant + def singleVar[$: P]: P[PVar] = P(fp.idnuse map (PVar(_) _)).pos // note that the parentheses are not redundant def vars_and_heap[$: P]: P[Seq[PFlowVar]] = (heap | singleVar).delimited(PSym.Comma).map(_.toSeq) def influenced_by[$: P]: P[PFlowAnnotation] = P(((heap | singleVar) ~ P(PByKeyword) ~/ vars_and_heap.braces) map { @@ -183,8 +183,10 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, case o@OldCall(methodName, args, rets, lbl) => /** check whether called method is a lemma */ val currmethod = input.findMethod(methodName) - var isLemma:Boolean = currmethod.pres.exists(p => p.isInstanceOf[Lemma]) - isLemma = isLemma || currmethod.posts.exists(p => p.isInstanceOf[Lemma]) + val isLemma = (currmethod.pres ++ currmethod.posts).exists { + case _: Lemma => true + case _ => false + } if (!isLemma) { reportError(ConsistencyError(s"method ${currmethod.name} called in old context must be lemma", o.pos)) @@ -230,7 +232,7 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, Seqn( new_pres.map(p => - Assert(LabelledOld(p, lbl.name)(p.pos))(o.pos) + Assert(LabelledOld(p, lbl)(p.pos))(o.pos) ) ++ @@ -241,7 +243,7 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, new_posts.map(p => - Inhale(LabelledOld(p, lbl.name)(p.pos))(o.pos) + Inhale(LabelledOld(p, lbl)(p.pos))(o.pos) ), new_v_decls )(o.pos) @@ -298,19 +300,16 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, /** add heap variables to vertices */ allVertices += (graph_analysis.heap_vertex -> graph_analysis.createInitialVertex(graph_analysis.heap_vertex)) - vars_outside_blk.foreach(v => { - if (v.isInstanceOf[LocalVarDecl]) { - val v_decl = v.asInstanceOf[LocalVarDecl] + vars_outside_blk.foreach { + case v_decl: LocalVarDecl => val v_init = graph_analysis.createInitialVertex(v_decl) allVertices += (v_decl -> v_init) /** add all variable to the graph */ graph.addVertex(v_init) graph.addVertex(v_decl) - } - }) - - + case _ => + } /** * get all variables that are assigned to inside the block and take intersection with universal introduction @@ -319,11 +318,9 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, val written_vars: Option[Set[LocalVarDecl]] = graph_analysis.getModifiedVars(allVertices ,blk) checkReassigned(written_vars, v, reportError, u) - /** execute modular flow analysis using graphs for the universal introduction statement */ graph_analysis.executeTaintedGraphAnalysis(tainted, blk, allVertices, u) - /** * SET VERSION */ @@ -332,7 +329,6 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, executeTaintedSetAnalysis(tainted_decls, vars_outside_blk, blk, u, reportError) */ - /** Translate the new syntax into Viper language */ val (new_v_map, new_exp1) = substituteWithFreshVars(v, exp1, usedNames) val new_exp2 = applySubstitution(new_v_map, exp2) @@ -340,7 +336,6 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, val new_trigs = trigs.map(t => Trigger(t.exps.map(e1 => applySubstitution(new_v_map, e1)))(t.pos)) val lbl = uniqueName("l", usedNames) - Seqn( Seq( Label(lbl, Seq())(u.pos), @@ -370,4 +365,4 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, */ newAst } -} \ No newline at end of file +} diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala index dc2baf1aa..2324b4789 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala @@ -14,11 +14,11 @@ import viper.silver.verifier.AbstractError import scala.jdk.CollectionConverters.CollectionHasAsScala trait SetGraphComparison extends VarAnalysisSet { - def computeSet(v: Declaration, blk: Stmt): Set[LocalVarDecl] = { + private def computeSet(v: Declaration, blk: Stmt): Set[LocalVarDecl] = { get_tainted_vars_stmt(Set(v), blk).map(v => v.asInstanceOf[LocalVarDecl]) } - def computeGraph(graph_analysis: VarAnalysisGraph, vars: Set[LocalVarDecl], blk: Stmt): (Graph[LocalVarDecl, DefaultEdge]/*, Map[LocalVarDecl,LocalVarDecl]*/) = { + private def computeGraph(graph_analysis: VarAnalysisGraph, vars: Set[LocalVarDecl], blk: Stmt): (Graph[LocalVarDecl, DefaultEdge]/*, Map[LocalVarDecl,LocalVarDecl]*/) = { var allVertices: Map[LocalVarDecl, LocalVarDecl] = Map.empty diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala index ab891f6bf..68b6a2224 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala @@ -535,7 +535,7 @@ case class VarAnalysisGraph(prog: Program, /** returned has to be instance of LocalVar */ val returned_var: LocalVar = returned match { - case v: Var => v.var_decl.asInstanceOf[LocalVar] + case v: Var => v.decl case _: Heap => heap_vertex.localVar } /** create LocalVarDecl such that it can be added in the graph */ @@ -549,7 +549,7 @@ case class VarAnalysisGraph(prog: Program, arguments.foreach(argument => { /** argument has to be instance of LocalVar */ val argument_var: LocalVar = argument match { - case v: Var => v.var_decl.asInstanceOf[LocalVar] + case v: Var => v.decl case _: Heap => heap_vertex.localVar } val argument_decl = LocalVarDecl(argument_var.name, argument_var.typ)(argument_var.pos) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala index 7a8006822..46d930d66 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala @@ -16,13 +16,11 @@ trait VarAnalysisSet { def reportErrorWithMsg(error: AbstractError): Unit - def executeTaintedSetAnalysis(tainted: Set[Declaration], vars_outside_blk: mutable.Set[Declaration], blk: Seqn, u: UniversalIntro, reportError: AbstractError => Unit): Unit = { /** check whether any additional variables are tainted inside of the block */ var all_tainted = Set[Declaration]() all_tainted = get_tainted_vars_stmt(tainted, blk) - /** remove the variables that were tainted to begin with */ vars_outside_blk --= mutable.Set(u.transitiveScopedDecls: _*) @@ -48,6 +46,7 @@ trait VarAnalysisSet { output = get_tainted_vars_stmt(output, s) } output + case LocalVarAssign(lhs, rhs) => if (is_expr_tainted(tainted, rhs)) { tainted ++ Set(LocalVarDecl(lhs.name, lhs.typ)(stmt.pos)) From 4847f5664066b5f76d92ea6e13a5964da282e7df Mon Sep 17 00:00:00 2001 From: Linard Arquint Date: Wed, 28 Feb 2024 16:24:37 +0100 Subject: [PATCH 14/41] changes yet another testcase that uses 'heap', which is now a keyword --- .../transformations/Performance/BinomialHeap.vpr | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test/resources/transformations/Performance/BinomialHeap.vpr b/src/test/resources/transformations/Performance/BinomialHeap.vpr index d8c17c505..c969f44aa 100644 --- a/src/test/resources/transformations/Performance/BinomialHeap.vpr +++ b/src/test/resources/transformations/Performance/BinomialHeap.vpr @@ -234,7 +234,7 @@ method findMinNode(arg: Ref) returns (res: Ref) field Nodes: Ref // BinomialHeapNode field size: Int // not read in the code, so we can omit it and simplify the predicates -predicate heap(this: Ref){ +predicate binheap(this: Ref){ acc(this.Nodes) && heapseg(this.Nodes, null) && sorted(this.Nodes, null) && (this.Nodes != null ==> segParent(this.Nodes, null) == null) && @@ -526,10 +526,10 @@ heapseg(this.Nodes, null) && sorted(this.Nodes, null) // in the callee method union or because it affects the fields we did // not model so far, namely size and parent. method extractMin(this: Ref) returns (res: Ref) - requires heap(this) - ensures heap(this) + requires binheap(this) + ensures binheap(this) { - unfold heap(this) + unfold binheap(this) var nodes: Ref := this.Nodes if(nodes == null){ res := null @@ -554,8 +554,8 @@ method extractMin(this: Ref) returns (res: Ref) invariant prevTemp != null && temp != minNode ==> treeDegree(prevTemp) < segDegree(temp, minNode, 0) invariant prevTemp != null && temp == minNode ==> treeDegree(prevTemp) < segDegree(minNode, null, 0) invariant temp != minNode ==> segDegree(temp, minNode, segLength(temp, minNode) - 1) < segDegree(minNode, null, 0) - invariant prevTemp != null ==> segSize(nodes, prevTemp) + treeSize(prevTemp) + segSize(temp, minNode) + segSize(minNode, null) == old(unfolding heap(this) in segSize(this.Nodes, null)) - invariant prevTemp == null ==> segSize(temp, minNode) + segSize(minNode, null) == old(unfolding heap(this) in segSize(this.Nodes, null)) + invariant prevTemp != null ==> segSize(nodes, prevTemp) + treeSize(prevTemp) + segSize(temp, minNode) + segSize(minNode, null) == old(unfolding binheap(this) in segSize(this.Nodes, null)) + invariant prevTemp == null ==> segSize(temp, minNode) + segSize(minNode, null) == old(unfolding binheap(this) in segSize(this.Nodes, null)) invariant temp != minNode ==> segParent(temp, minNode) == null invariant minNode != null ==> segParent(minNode, null) == null invariant prevTemp != null && nodes != prevTemp ==> segParent(nodes, prevTemp) == null @@ -637,5 +637,5 @@ method extractMin(this: Ref) returns (res: Ref) res := minNode } - fold heap(this) + fold binheap(this) } From 7d05083329f25c4d51eaf32ae3b4d9c03cf740d1 Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Tue, 9 Apr 2024 09:53:32 +0200 Subject: [PATCH 15/41] first version of graph map taint analysis --- .../reasoning/BeforeVerifyHelper.scala | 6 +- .../standard/reasoning/ReasoningPlugin.scala | 62 +---- .../analysis/VarAnalysisGraphMap.scala | 245 ++++++++++++++++++ 3 files changed, 255 insertions(+), 58 deletions(-) create mode 100644 src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala index 08050f7cd..60ec4292f 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala @@ -53,15 +53,13 @@ trait BeforeVerifyHelper { * @param reportError: Method to report the error when a qunatified variable was modified * @param u: universal introduction statement, used for details in error message */ - def checkReassigned(modified_vars: Option[Set[LocalVarDecl]], quantified_vars: Seq[LocalVarDecl], reportError: AbstractError => Unit, u: UniversalIntro): Unit = { - if (modified_vars.isDefined) { - val reassigned_vars: Set[LocalVarDecl] = modified_vars.get.intersect(quantified_vars.toSet) + def checkReassigned(modified_vars: Set[LocalVarDecl], quantified_vars: Seq[LocalVarDecl], reportError: AbstractError => Unit, u: UniversalIntro): Unit = { + val reassigned_vars: Set[LocalVarDecl] = modified_vars.intersect(quantified_vars.toSet) if (reassigned_vars.nonEmpty) { val reassigned_names: String = reassigned_vars.mkString(", ") val reassigned_pos: String = reassigned_vars.map(_.pos).mkString(", ") reportError(ConsistencyError("Universal Introduction variable(s) (" + reassigned_names + ") might have been reassigned at position(s) (" + reassigned_pos + ")", u.pos)) } - } } private def specifiesLemma(m: Method): Boolean = (m.pres ++ m.posts).exists { diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index 0df3b9572..ad1fbaa38 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -7,14 +7,12 @@ package viper.silver.plugin.standard.reasoning import fastparse._ -import org.jgrapht.Graph -import org.jgrapht.graph.{DefaultDirectedGraph, DefaultEdge} import viper.silver.ast._ import viper.silver.ast.utility.rewriter.{StrategyBuilder, Traverse} import viper.silver.ast.utility.ViperStrategy import viper.silver.parser.FastParserCompanion.whitespace import viper.silver.parser._ -import viper.silver.plugin.standard.reasoning.analysis.{SetGraphComparison, VarAnalysisGraph} +import viper.silver.plugin.standard.reasoning.analysis.{SetGraphComparison, VarAnalysisGraphMap} import viper.silver.plugin.{ParserPluginTemplate, SilverPlugin} import viper.silver.verifier._ @@ -22,7 +20,7 @@ import scala.annotation.unused import scala.collection.mutable class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, - @unused logger: ch.qos.logback.classic.Logger, + logger: ch.qos.logback.classic.Logger, @unused config: viper.silver.frontend.SilFrontendConfig, fp: FastParser) extends SilverPlugin with ParserPluginTemplate with SetGraphComparison with BeforeVerifyHelper { @@ -268,66 +266,22 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, case u@UniversalIntro(v, trigs, exp1, exp2, blk) => val boolvar = LocalVarDecl(uniqueName("b", usedNames), Bool)(exp1.pos) - val vars_outside_blk: mutable.Set[Declaration] = mutable.Set() - /** Get all variables that are in scope in the current method but not inside the block */ - input.methods.foreach(m => m.body.get.ss.foreach(s => { - if (s.contains(u)) { - vars_outside_blk ++= mutable.Set(m.transitiveScopedDecls: _*) - } - })) - - /** Qunatified variables in the universal introduction statement are tainted */ val tainted: Set[LocalVarDecl] = v.toSet - - - /** - * GRAPH VERSION - */ - - val graph_analysis: VarAnalysisGraph = VarAnalysisGraph(input, reportError) - + val vars_outside_blk = (input.methods.flatMap(m => m.body.get.ss.filter(s => s.contains(u)).flatMap(_ => Set(m.transitiveScopedDecls: _*))).toSet -- Set(u.transitiveScopedDecls: _*)) ++ tainted /** create graph with vars that are in scope only outside of the universal introduction code block including the qunatified variables*/ - vars_outside_blk --= mutable.Set(u.transitiveScopedDecls: _*) - vars_outside_blk ++= v - - val graph: Graph[LocalVarDecl, DefaultEdge] = new DefaultDirectedGraph[LocalVarDecl, DefaultEdge](classOf[DefaultEdge]) - - /** Map that contains all variables where the key is represents the variables final value and the value the variables initial value before a statement. */ - var allVertices: Map[LocalVarDecl, LocalVarDecl] = Map[LocalVarDecl, LocalVarDecl]() - - /** add heap variables to vertices */ - allVertices += (graph_analysis.heap_vertex -> graph_analysis.createInitialVertex(graph_analysis.heap_vertex)) - - vars_outside_blk.foreach { - case v_decl: LocalVarDecl => - val v_init = graph_analysis.createInitialVertex(v_decl) - allVertices += (v_decl -> v_init) - - /** add all variable to the graph */ - graph.addVertex(v_init) - graph.addVertex(v_decl) - case _ => - } - + val analysis: VarAnalysisGraphMap = VarAnalysisGraphMap(input, logger, reportError) /** * get all variables that are assigned to inside the block and take intersection with universal introduction * variables. If they are contained throw error since quantified variables should be immutable */ - val written_vars: Option[Set[LocalVarDecl]] = graph_analysis.getModifiedVars(allVertices ,blk) + val written_vars: Set[LocalVarDecl] = analysis.getModifiedVars(blk) checkReassigned(written_vars, v, reportError, u) - /** execute modular flow analysis using graphs for the universal introduction statement */ - graph_analysis.executeTaintedGraphAnalysis(tainted, blk, allVertices, u) - - /** - * SET VERSION - */ - /* - val tainted_decls: Set[Declaration] = tainted.map(t => t.asInstanceOf[Declaration]) - executeTaintedSetAnalysis(tainted_decls, vars_outside_blk, blk, u, reportError) - */ + val volatileVars: Set[LocalVarDecl] = analysis.getVarsFromExpr(exp1) ++ analysis.getVarsFromExpr(exp2) -- v + /** execute modular flow analysis using graph maps for the universal introduction statement */ + analysis.executeTaintedGraphAnalysis(vars_outside_blk.collect({ case v:LocalVarDecl => v }), tainted, blk, volatileVars, u) /** Translate the new syntax into Viper language */ val (new_v_map, new_exp1) = substituteWithFreshVars(v, exp1, usedNames) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala new file mode 100644 index 000000000..cbb490800 --- /dev/null +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -0,0 +1,245 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2024 ETH Zurich. + +package viper.silver.plugin.standard.reasoning.analysis + +import viper.silver.ast.{AccessPredicate, Apply, Assert, Assume, BinExp, CurrentPerm, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, Fold, ForPerm, FuncApp, Goto, If, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Package, Program, Ref, Seqn, Stmt, UnExp, Unfold, While} +import viper.silver.plugin.standard.reasoning.{ExistentialElim, FlowAnnotation, FlowVar, Heap, OldCall, UniversalIntro, Var} +import viper.silver.verifier.{AbstractError, ConsistencyError} + + +case class VarAnalysisGraphMap(prog: Program, + logger: ch.qos.logback.classic.Logger, + reportErrorWithMsg: AbstractError => Unit) { + + val prefix: String = ".init_" + val heap_vertex: LocalVarDecl = LocalVarDecl(".heap", Ref)() + private type GraphMap = Map[LocalVarDecl, Set[LocalVarDecl]] + + + /** execute the information flow analysis with graphs. + * When executed on the universal introduction statement the tainted variables are simply the quantified variables */ + def executeTaintedGraphAnalysis(allVars: Set[LocalVarDecl], tainted: Set[LocalVarDecl], blk: Seqn, volatileVars: Set[LocalVarDecl], u: UniversalIntro): Unit = { + // Build initial graph where every variable and the heap influences itself + val initialGraph = allVars.map(k => k -> Set(k)).toMap + (heap_vertex -> Set(heap_vertex)) + + val graph = computeInfluenceMap(blk, initialGraph, Set(), tainted) + val problems = volatileVars.filter(v => graph(v).intersect(tainted).nonEmpty) + + + if(problems.nonEmpty) { + reportErrorWithMsg(ConsistencyError("Universal introduction variable might have been assigned to variable " + problems + ", used in the quantified implication", u.pos)) + } + } + + private def computeInfluenceMap(stmt: Stmt, graphMap: GraphMap, assumeInfluences: Set[LocalVarDecl], tainted: Set[LocalVarDecl]): GraphMap = { + logger.warn("Statement: " + stmt) + stmt match { + // TODO Question: do scopedSeqnDeclarations overwrite existing variables in the subgraphs?? + case Seqn(ss, scopedSeqnDeclarations) => + // TODO Question: Idea is, I overwrite all scoped declarations with a variable that only influences itseld + // And afterwards restore the influence before the scoped block, is this correct? + val declarations = scopedSeqnDeclarations.collect({ case l: LocalVarDecl => l }) + val scopedGraph = graphMap ++ declarations.map(decl => decl -> Set(decl)).toMap + ss.foldLeft(scopedGraph) { (graph, subStmt) => computeInfluenceMap(subStmt, graph, assumeInfluences, tainted) } + // TODO Question: Havoc? + // case Havoc(_) => ? + case LocalVarAssign(lhs, rhs) => + val influences = getVarsFromExpr(rhs).flatMap(v => graphMap(v)) + graphMap + (getLocalVarDeclFromLocalVar(lhs) -> influences) + case UniversalIntro(quantifiedVars, _, _, _, s) => computeInfluenceMap(s, graphMap ++ quantifiedVars.map(v => v -> Set()), assumeInfluences, tainted ++ quantifiedVars) + case If(cond, thn, els) => + val conditionVars = getVarsFromExpr(cond) + + // For the condition influences, we only care for variables that are declared outside of the if block + val writesInIfBlocks = (getModifiedVars(thn) ++ getModifiedVars(els)).filter(v => graphMap.contains(v)); + val conditionInfluences = writesInIfBlocks.map(v => v -> (graphMap(v) ++ conditionVars.flatMap(c => graphMap(c)))).toMap + + val thn_graph = computeInfluenceMap(thn, graphMap, assumeInfluences ++ conditionVars, tainted) + val els_graph = computeInfluenceMap(els, graphMap, assumeInfluences ++ conditionVars, tainted) + thn_graph.keySet.union(els_graph.keySet).map( + v => v -> (thn_graph.getOrElse(v, Set()) ++ els_graph.getOrElse(v, Set()) ++ conditionInfluences.getOrElse(v, Set())) + ).toMap + case While(cond, _, body) => + // TODO Question: This seems like quite a smart way to do this, can we keep it this way? + var iteration_graph = computeInfluenceMap(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), graphMap, assumeInfluences, tainted) + var edges_equal: Boolean = false + var merge_graph = iteration_graph + var iterations = 1; + while (!edges_equal) { + iteration_graph = computeInfluenceMap(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), merge_graph, assumeInfluences, tainted) + if(iteration_graph.equals(merge_graph)) { + edges_equal = true + } else { + merge_graph = iteration_graph + } + iterations += 1; + } + merge_graph + case MethodCall(methodName, args, targets) => + val met = prog.findMethod(methodName) + computeMethodInfluenceMap(graphMap, args, targets, met.formalArgs, met.formalReturns, met.posts) + + case OldCall(methodName, args, targets, _) => + val met = prog.findMethod(methodName) + computeMethodInfluenceMap(graphMap, args, targets, met.formalArgs, met.formalReturns, met.posts) + + case FieldAssign(_, rhs) => + val vars = getVarsFromExpr(rhs) + graphMap + (heap_vertex -> (graphMap(heap_vertex) ++ vars.filter(v => !v.equals(heap_vertex)).flatMap(v => graphMap(v)))) + case Exhale(exp) => + if (exp.isPure) { + graphMap + } else { + val vars = getVarsFromExpr(exp) + graphMap + (heap_vertex -> (graphMap(heap_vertex) ++ vars)) + } + case Fold(acc) => + val vars = getVarsFromExpr(acc) + graphMap + (heap_vertex -> (graphMap(heap_vertex) ++ vars)) + case Unfold(acc) => + val vars = getVarsFromExpr(acc) + graphMap + (heap_vertex -> (graphMap(heap_vertex) ++ vars)) + case Apply(exp) => + val vars = getVarsFromExpr(exp) + graphMap + (heap_vertex -> (graphMap(heap_vertex) ++ vars)) + case Package(wand, _) => + val vars = getVarsFromExpr(wand) + graphMap + (heap_vertex -> (graphMap(heap_vertex) ++ vars)) + + + case a: Inhale => + val assumeVars = (assumeInfluences ++ getVarsFromExpr(a.exp)).flatMap(v => graphMap(v) + v).intersect(tainted) + logger.warn("Assume vars: " + assumeVars) + if (assumeVars.nonEmpty) { + reportErrorWithMsg(ConsistencyError(s"Universal introduction variable might influence assume statement: $a ", a.pos)) + } + graphMap + case a: Assume => + val assumeVars = (assumeInfluences ++ getVarsFromExpr(a.exp)).flatMap(v => graphMap(v) + v).intersect(tainted) + logger.warn("Assume vars: " + assumeVars) + if (assumeVars.nonEmpty) { + reportErrorWithMsg(ConsistencyError(s"Universal introduction variable might influence assume statement: $a ", a.pos)) + } + graphMap + case Assert(_) => graphMap + case Label(_, _) => graphMap + case e: ExistentialElim => + reportErrorWithMsg(ConsistencyError(s"$e is an undefined statement for the modular information flow analysis", e.pos)) + graphMap + case g: Goto => + reportErrorWithMsg(ConsistencyError(s"$g is an undefined statement for the modular information flow analysis", g.pos)) + graphMap + case _ => + reportErrorWithMsg(ConsistencyError(s"$stmt is an undefined statement for the modular information flow analysis", stmt.pos)) + graphMap + } + } + + /** + * returns all the variables inside an expression + * @param graph existing graph + * @param exp expressions from which all variables should be returned + * @return set of Variable declarations + */ + def getVarsFromExpr(exp: Exp): Set[LocalVarDecl] = { + val vars: Set[LocalVarDecl] = Set() + exp match { + case l@LocalVar(_, _) => Set(getLocalVarDeclFromLocalVar(l)) + case BinExp(exp1, exp2) => getVarsFromExpr(exp1) ++ getVarsFromExpr(exp2) + case UnExp(exp) => getVarsFromExpr(exp) + case FuncApp(_, exps) => + // TODO Question: how do we differentiate between functions & methods -> functions are side-effect free, so the heap is not needed there + // TODO Question: Couldn't we use our method influence analysis here as well as an easy way to not over approximate? + exps.flatMap(e => getVarsFromExpr(e).filter(v => v.typ != Ref)).toSet // + heap_vertex + case DomainFuncApp(_, exps, _) => + vars ++ exps.flatMap(e => getVarsFromExpr(e)) + case _: ForPerm | _: CurrentPerm => + Set(heap_vertex) + case FieldAccess(v, _) => + getVarsFromExpr(v) + heap_vertex + case AccessPredicate(access, _) => + /** Should only be the case in e.g.an inhale or an exhale statement */ + var allVars = vars + val access_vars = getVarsFromExpr(access) + access_vars.foreach(v => { + allVars += v + }) + allVars + case _ => Set() + } + } + + /** + * creates an edge between every variable in the expression to every variable that is in scope and returns resulting graph + */ + private def expInfluencesAllVertices(exp:Exp, graphMap: GraphMap) : GraphMap = { + // TODO Question: Is this correct? (used by Inhale) + val vars = getVarsFromExpr(exp) + val influences = vars.flatMap(v => graphMap(v)) + graphMap.map(f => f._1 -> influences) + } + + /** creates graph for methodcall and oldcall. maps expressions passed to methods to the method arguments, computes the graph based on the flow annotation, + * and finally maps the return variables to the variables that the method is assigned to. */ + private def computeMethodInfluenceMap(graphMap: GraphMap, argExpressions: Seq[Exp], retVars: Seq[LocalVar], methodArgVars: Seq[LocalVarDecl], methodRetVars: Seq[LocalVarDecl], posts: Seq[Exp]): GraphMap = { + /** set of all target variables that have not been included in the influenced by expression up until now */ + val retSet: Set[LocalVarDecl] = methodRetVars.toSet + heap_vertex + + /** create .arg_ declaration for each argument */ + val methodArgExpMapping = (methodArgVars zip argExpressions).map(method_arg => { + method_arg._1 -> getVarsFromExpr(method_arg._2) + }).toMap + + val allMethodArgsSet = methodArgExpMapping.values.flatten.toSet + heap_vertex + + val retVarMapping = (retVars.map(l => getLocalVarDeclFromLocalVar(l)) zip methodRetVars) + .map(vars => vars._2 -> vars._1).toMap + (heap_vertex -> heap_vertex) + + /** Get influences defined in the influenced annotations */ + val annotationInfluences = posts.collect({case FlowAnnotation(returned, arguments) => (returned, arguments)}).map(t => { + val decl = retVarMapping(getLocalVarDeclFromFlowVar(t._1)) + decl -> (t._2.flatMap(v => methodArgExpMapping(getLocalVarDeclFromFlowVar(v))).toSet ++ graphMap(decl)) + }).toMap + + /** influence all return variables, not mentioned by annotation, by every method argument */ + val otherInfluences = (retSet -- annotationInfluences.keySet).map(retVar => { + val decl = retVarMapping(retVar) + decl -> (allMethodArgsSet ++ graphMap(decl)) + }) + val methodInfluences = annotationInfluences ++ otherInfluences + + graphMap ++ methodInfluences + } + + private def getLocalVarDeclFromFlowVar(f: FlowVar): LocalVarDecl = { + f match { + case v: Var => LocalVarDecl(v.decl.name, v.decl.typ)(v.decl.pos) + case _: Heap => heap_vertex + } + } + + /** + * get the variables that were modified by the statement stmt + */ + def getModifiedVars(stmt: Stmt): Set[LocalVarDecl] = { + stmt match { + case Seqn(ss, _) => ss.flatMap(s => getModifiedVars(s)).toSet + case LocalVarAssign(l, _) => Set(getLocalVarDeclFromLocalVar(l)) + case If(_, thn, els) => getModifiedVars(thn) ++ getModifiedVars(els) + case While(_, _, body) => getModifiedVars(body) + case MethodCall(_, _, _) => Set() + case Inhale(_) => Set() + case Assume(_) => Set() + case Label(_, _) => Set() + case _ => Set() + } + } + + private def getLocalVarDeclFromLocalVar(l: LocalVar): LocalVarDecl = { + LocalVarDecl(l.name, l.typ)() + } +} From 4153019050dd80c5cb1312e8aec9b1ec47ba541f Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Tue, 16 Apr 2024 14:58:37 +0200 Subject: [PATCH 16/41] added assume analysis & modular method analysis --- .../reasoning/BeforeVerifyHelper.scala | 106 +-- .../standard/reasoning/ReasoningPlugin.scala | 73 +- .../analysis/SetGraphComparison.scala | 152 ---- .../reasoning/analysis/VarAnalysisGraph.scala | 692 ------------------ .../analysis/VarAnalysisGraphMap.scala | 386 ++++++---- .../reasoning/analysis/VarAnalysisSet.scala | 177 ----- 6 files changed, 260 insertions(+), 1326 deletions(-) delete mode 100644 src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala delete mode 100644 src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala delete mode 100644 src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala index 60ec4292f..108be85e2 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala @@ -6,15 +6,13 @@ package viper.silver.plugin.standard.reasoning -import org.jgrapht.graph.DefaultEdge + import viper.silver.ast.utility.Expressions -import viper.silver.ast.{Apply, Exhale, Exp, FieldAssign, Fold, Inhale, LocalVar, LocalVarDecl, Method, MethodCall, Package, Position, Program, Seqn, Stmt, Unfold} -import viper.silver.plugin.standard.reasoning.analysis.VarAnalysisGraph +import viper.silver.ast.{Apply, Exhale, Exp, FieldAssign, Fold, Inhale, LocalVarDecl, Method, MethodCall, Package, Program, Seqn, Stmt, Unfold} import viper.silver.plugin.standard.termination.{DecreasesSpecification, DecreasesStar, DecreasesTuple, DecreasesWildcard} import viper.silver.verifier.{AbstractError, ConsistencyError} import scala.collection.mutable -import scala.jdk.CollectionConverters.CollectionHasAsScala trait BeforeVerifyHelper { /** methods to rename variables for the encoding of the new syntax */ @@ -152,104 +150,4 @@ trait BeforeVerifyHelper { pure } } - - /** check that influenced by expressions are exact or overapproximate the body of the method. */ - def checkInfluencedBy(input: Program, reportError: AbstractError => Unit): Unit = { - val body_graph_analysis: VarAnalysisGraph = VarAnalysisGraph(input, reportError) - input.methods.foreach(method => { - var return_vars = method.formalReturns.toSet ++ Seq(body_graph_analysis.heap_vertex) - val arg_vars = method.formalArgs.toSet ++ Seq(body_graph_analysis.heap_vertex) - - /** helper variable for error message, maps local variable to its position */ - var influenced_by_pos: Map[LocalVarDecl, Position] = Map() - - - val init_args_decl = method.formalArgs.map(a => body_graph_analysis.createInitialVertex(a)) - val init_rets_decl = method.formalReturns.map(r => body_graph_analysis.createInitialVertex(r)) - val method_vars: Map[LocalVarDecl, LocalVarDecl] = ((method.formalArgs zip init_args_decl) ++ (method.formalReturns zip init_rets_decl) ++ Seq((body_graph_analysis.heap_vertex, body_graph_analysis.createInitialVertex(body_graph_analysis.heap_vertex)))).toMap - val empty_body_graph = body_graph_analysis.createEmptyGraph(method_vars) - val heap_vert = LocalVarDecl(body_graph_analysis.heap_vertex.name, body_graph_analysis.heap_vertex.typ)() - - val influenced_graph = body_graph_analysis.copyGraph(empty_body_graph) - var influenced_exists: Boolean = false - /** iterate through method postconditions to find flow annotations */ - method.posts.foreach { - case v@FlowAnnotation(target, args) => - influenced_exists = true - - /** create target variable of flowannotation based on whether it is the heap or another return variable */ - val target_var: LocalVar = target match { - case value: Var => value.decl - case _ => body_graph_analysis.heap_vertex.localVar - } - val target_decl: LocalVarDecl = LocalVarDecl(target_var.name, target_var.typ)(v.pos) - - /** check whether the target variable is in fact a return variable */ - if (!return_vars.contains(target_decl)) { - reportError(ConsistencyError(s"Only return variables can be influenced and only one influenced by expression per return variable can exist. ${target_decl.name} may not be a return variable or might be used several times.", v.pos)) - } - /** keep track of which return variables have an influenced by annotation */ - return_vars -= target_decl - influenced_by_pos += (target_decl -> v.pos) - - - args.foreach(arg => { - - /** decide for each variable in the set of variables of the flow annotation whether they represent a normal variable or the heap */ - val arg_var: LocalVar = arg match { - case value: Var => value.decl - case _ => body_graph_analysis.heap_vertex.localVar - } - val arg_decl: LocalVarDecl = LocalVarDecl(arg_var.name, arg_var.typ)(arg_var.pos) - - /** check that each variable in the set is a method argument */ - if (!arg_vars.contains(arg_decl)) { - reportError(ConsistencyError(s"Only argument variables can be influencing the return variable. ${arg_decl.name} may not be an argument variable.", v.pos)) - } - - /** add corresponding edge from method argument to the target variable */ - influenced_graph.addEdge(method_vars(arg_decl), target_decl, new DefaultEdge) - }) - case _ => () - - } - /** for all remaining variables that didn't have an influenced by annotation create an edge from every method argument to the return variable - * to overapproximate the information flow - */ - return_vars.foreach(rv => { - (method.formalArgs ++ Seq(body_graph_analysis.heap_vertex)).foreach(a => { - if (!influenced_graph.containsVertex(method_vars(a))) { - influenced_graph.addVertex(method_vars(a)) - } - if (!influenced_graph.containsVertex(rv)) { - influenced_graph.addVertex(rv) - } - influenced_graph.addEdge(method_vars(a), rv, new DefaultEdge) - }) - }) - - /** for evaluation purposes if graph of method body should be created even though there is no influenced by annotation */ - //val body_graph = body_graph_analysis.compute_graph(method.body.getOrElse(Seqn(Seq(), Seq())()), method_vars) - - /** if influenced by annotation exists create graph of the method body and check whether the influenced by expression is correct */ - if (influenced_exists) { - val body_graph = body_graph_analysis.compute_graph(method.body.getOrElse(Seqn(Seq(), Seq())()), method_vars) - - /** ignore the edges from the .init_ret to the ret vertex since before the method there is no init value of a return variable. */ - method.formalReturns.foreach(r => { - body_graph.removeAllEdges(method_vars(r), r) - }) - - /** the set of all incoming edges to the return variables of the method body graph should be a subset of the set of the incoming edges of the influenced by graph */ - (method.formalReturns ++ Seq(heap_vert)).foreach(r => { - body_graph.incomingEdgesOf(r).forEach(e => { - if (!influenced_graph.containsEdge(body_graph.getEdgeSource(e), body_graph.getEdgeTarget(e))) { - val ret_sources: String = body_graph.incomingEdgesOf(r).asScala.map(e => body_graph.getEdgeSource(e).name).toList.sortWith(_ < _).mkString(", ").replace(".init_", "").replace(".", "") - reportError(ConsistencyError("influenced by expression may be incorrect. Possible influenced by expression: \n" + "influenced " + r.name.replace(".", "") + " by {" + ret_sources + "}", influenced_by_pos(r))) - } - }) - }) - } - }) - } } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index ad1fbaa38..bf9838e8b 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -12,7 +12,7 @@ import viper.silver.ast.utility.rewriter.{StrategyBuilder, Traverse} import viper.silver.ast.utility.ViperStrategy import viper.silver.parser.FastParserCompanion.whitespace import viper.silver.parser._ -import viper.silver.plugin.standard.reasoning.analysis.{SetGraphComparison, VarAnalysisGraphMap} +import viper.silver.plugin.standard.reasoning.analysis.VarAnalysisGraphMap import viper.silver.plugin.{ParserPluginTemplate, SilverPlugin} import viper.silver.verifier._ @@ -22,36 +22,33 @@ import scala.collection.mutable class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, logger: ch.qos.logback.classic.Logger, @unused config: viper.silver.frontend.SilFrontendConfig, - fp: FastParser) extends SilverPlugin with ParserPluginTemplate with SetGraphComparison with BeforeVerifyHelper { + fp: FastParser) extends SilverPlugin with ParserPluginTemplate with BeforeVerifyHelper { import fp.{exp, ParserExtension, lineCol, _file} import FastParserCompanion.{ExtendedParsing, PositionParsing, reservedKw, reservedSym} - - override def reportErrorWithMsg(error: AbstractError): Unit = reportError(error) - /** Parser for existential elimination statements. */ - def existential_elim[$: P]: P[PExistentialElim] = + def existentialElim[$: P]: P[PExistentialElim] = P((P(PObtainKeyword) ~/ fp.nonEmptyIdnTypeList(PLocalVarDecl(_)) ~/ P(PWhereKeyword) ~/ fp.trigger.rep ~ exp).map { case (obtainKw, varDecls, whereKw, triggers, e) => PExistentialElim(obtainKw, varDecls, whereKw, triggers, e)(_) }).pos /** Parser for universal introduction statements. */ - def universal_intro[$: P]: P[PUniversalIntro] = + def universalIntro[$: P]: P[PUniversalIntro] = P((P(PProveKeyword) ~/ PKw.Forall ~/ fp.nonEmptyIdnTypeList(PLocalVarDecl(_)) ~/ fp.trigger.rep ~/ P(PAssumingKeyword) ~/ exp ~/ P(PImpliesKeyword) ~/ exp ~/ fp.stmtBlock()).map { case (proveKw, forallKw, varDecls, triggers, assumingKw, p, impliesKw, q, b) => PUniversalIntro(proveKw, forallKw, varDecls, triggers, assumingKw, p, impliesKw, q, b)(_) }).pos /** Parser for new influence by condition */ def flowSpec[$: P]: P[PSpecification[PInfluencedKeyword.type]] = - P((P(PInfluencedKeyword) ~ influenced_by) map (PSpecification.apply _).tupled).pos + P((P(PInfluencedKeyword) ~ influencedBy) map (PSpecification.apply _).tupled).pos def heap[$: P]: P[PHeap] = P(P(PHeapKeyword) map (PHeap(_) _)).pos // note that the parentheses are not redundant def singleVar[$: P]: P[PVar] = P(fp.idnuse map (PVar(_) _)).pos // note that the parentheses are not redundant - def vars_and_heap[$: P]: P[Seq[PFlowVar]] = (heap | singleVar).delimited(PSym.Comma).map(_.toSeq) + def varsAndHeap[$: P]: P[Seq[PFlowVar]] = (heap | singleVar).delimited(PSym.Comma).map(_.toSeq) - def influenced_by[$: P]: P[PFlowAnnotation] = P(((heap | singleVar) ~ P(PByKeyword) ~/ vars_and_heap.braces) map { + def influencedBy[$: P]: P[PFlowAnnotation] = P(((heap | singleVar) ~ P(PByKeyword) ~/ varsAndHeap.braces) map { case (v, byKw, groupedVarList) => PFlowAnnotation(v, byKw, groupedVarList)(_) }).pos @@ -95,8 +92,8 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, ParserExtension.addNewKeywords(Set(POldCallKeyword)) /** adding existential elimination and universal introduction to the parser */ - ParserExtension.addNewStmtAtEnd(existential_elim(_)) - ParserExtension.addNewStmtAtEnd(universal_intro(_)) + ParserExtension.addNewStmtAtEnd(existentialElim(_)) + ParserExtension.addNewStmtAtEnd(universalIntro(_)) /** add influenced by flow annotation to as a postcondition */ ParserExtension.addNewPostCondition(flowSpec(_)) @@ -129,30 +126,18 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, override def beforeVerify(input: Program): Program = { - - /** for evaluation purposes */ - //val begin_time = System.currentTimeMillis() - val usedNames: mutable.Set[String] = collection.mutable.Set(input.transitiveScopedDecls.map(_.name): _*) /** check that lemma terminates (has a decreases clause) and that it is pure */ checkLemma(input, reportError) - - /** check that influenced by expressions are exact or overapproximate the body of the method. */ - checkInfluencedBy(input, reportError) - - - /** method call to compare the analysis of the set-approach vs. the graph approach */ - //compareGraphSet(input, reportError) - + /** create graph with vars that are in scope only outside of the universal introduction code block including the qunatified variables */ + val analysis: VarAnalysisGraphMap = VarAnalysisGraphMap(input, logger, reportError) val newAst: Program = ViperStrategy.Slim({ /** remove the influenced by postconditions. * remove isLemma */ case m: Method => - - var postconds: Seq[Exp] = Seq() m.posts.foreach { case _: FlowAnnotation => @@ -227,7 +212,6 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, rTov += (r -> new_v) } - Seqn( new_pres.map(p => Assert(LabelledOld(p, lbl)(p.pos))(o.pos) @@ -246,7 +230,6 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, new_v_decls )(o.pos) - case e@ExistentialElim(v, trigs, exp) => val (new_v_map, new_exp) = substituteWithFreshVars(v, exp, usedNames) val new_trigs = trigs.map(t => Trigger(t.exps.map(e1 => applySubstitution(new_v_map, e1)))(t.pos)) @@ -266,28 +249,26 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, case u@UniversalIntro(v, trigs, exp1, exp2, blk) => val boolvar = LocalVarDecl(uniqueName("b", usedNames), Bool)(exp1.pos) - /** Get all variables that are in scope in the current method but not inside the block */ + /** Get all variables that are in scope in the current method */ val tainted: Set[LocalVarDecl] = v.toSet - val vars_outside_blk = (input.methods.flatMap(m => m.body.get.ss.filter(s => s.contains(u)).flatMap(_ => Set(m.transitiveScopedDecls: _*))).toSet -- Set(u.transitiveScopedDecls: _*)) ++ tainted - - /** create graph with vars that are in scope only outside of the universal introduction code block including the qunatified variables*/ - val analysis: VarAnalysisGraphMap = VarAnalysisGraphMap(input, logger, reportError) + val varsOutside = (input.methods.flatMap(m => m.body.get.ss.filter(s => s.contains(u)).flatMap(_ => Set(m.transitiveScopedDecls: _*))).toSet -- Set(u.transitiveScopedDecls: _*)) ++ tainted /** * get all variables that are assigned to inside the block and take intersection with universal introduction * variables. If they are contained throw error since quantified variables should be immutable */ - val written_vars: Set[LocalVarDecl] = analysis.getModifiedVars(blk) - checkReassigned(written_vars, v, reportError, u) + val writtenVars: Set[LocalVarDecl] = analysis.getModifiedVars(blk) + checkReassigned(writtenVars, v, reportError, u) - val volatileVars: Set[LocalVarDecl] = analysis.getVarsFromExpr(exp1) ++ analysis.getVarsFromExpr(exp2) -- v + /** Contains all variables that must not be tainted */ + val volatileVars: Set[LocalVarDecl] = analysis.getLocalVarDeclsFromExpr(exp1) ++ analysis.getLocalVarDeclsFromExpr(exp2) -- v /** execute modular flow analysis using graph maps for the universal introduction statement */ - analysis.executeTaintedGraphAnalysis(vars_outside_blk.collect({ case v:LocalVarDecl => v }), tainted, blk, volatileVars, u) + analysis.executeTaintedGraphAnalysis(varsOutside.collect({ case v:LocalVarDecl => v }), tainted, blk, volatileVars, u) /** Translate the new syntax into Viper language */ - val (new_v_map, new_exp1) = substituteWithFreshVars(v, exp1, usedNames) - val new_exp2 = applySubstitution(new_v_map, exp2) - val arb_vars = new_v_map.map(vars => vars._2) - val new_trigs = trigs.map(t => Trigger(t.exps.map(e1 => applySubstitution(new_v_map, e1)))(t.pos)) + val (newVarMap, newExp1) = substituteWithFreshVars(v, exp1, usedNames) + val newExp2 = applySubstitution(newVarMap, exp2) + val quantifiedVars = newVarMap.map(vars => vars._2) + val newTrigs = trigs.map(t => Trigger(t.exps.map(e1 => applySubstitution(newVarMap, e1)))(t.pos)) val lbl = uniqueName("l", usedNames) Seqn( @@ -304,19 +285,13 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, )(exp1.pos), blk, Assert(Implies(boolvar.localVar, exp2)(exp2.pos))(exp2.pos), - Inhale(Forall(arb_vars, new_trigs, Implies(LabelledOld(new_exp1, lbl)(exp2.pos), new_exp2)(exp2.pos))(exp2.pos))(exp2.pos) + Inhale(Forall(quantifiedVars, newTrigs, Implies(LabelledOld(newExp1, lbl)(exp2.pos), newExp2)(exp2.pos))(exp2.pos))(exp2.pos) ), Seq(boolvar) ++ v )(exp1.pos) }, Traverse.TopDown).execute[Program](input) - /** for evaluation purposes */ - /* - val end_time = System.currentTimeMillis() - println("--------------------------------------------------------------------------") - println("beforeVerify time: " + (end_time - begin_time) + "ms") - println("--------------------------------------------------------------------------") - */ + newAst } } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala deleted file mode 100644 index 2324b4789..000000000 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/SetGraphComparison.scala +++ /dev/null @@ -1,152 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2011-2024 ETH Zurich. - -package viper.silver.plugin.standard.reasoning.analysis - -import org.jgrapht.Graph -import org.jgrapht.graph.DefaultEdge -import viper.silver.ast.{Declaration, LocalVarDecl, Program, Stmt} -import viper.silver.verifier.AbstractError - -import scala.jdk.CollectionConverters.CollectionHasAsScala - -trait SetGraphComparison extends VarAnalysisSet { - private def computeSet(v: Declaration, blk: Stmt): Set[LocalVarDecl] = { - get_tainted_vars_stmt(Set(v), blk).map(v => v.asInstanceOf[LocalVarDecl]) - } - - private def computeGraph(graph_analysis: VarAnalysisGraph, vars: Set[LocalVarDecl], blk: Stmt): (Graph[LocalVarDecl, DefaultEdge]/*, Map[LocalVarDecl,LocalVarDecl]*/) = { - - var allVertices: Map[LocalVarDecl, LocalVarDecl] = Map.empty - - /** add heap variables to vertices */ - allVertices += (graph_analysis.heap_vertex -> graph_analysis.createInitialVertex(graph_analysis.heap_vertex)) - - vars.foreach(v => { - val v_decl = v - val v_init = graph_analysis.createInitialVertex(v_decl) - allVertices += (v_decl -> v_init) - - }) - graph_analysis.compute_graph(blk, allVertices) - } - - def compareGraphSet(prog: Program, reportError: AbstractError => Unit): Unit = { - - /** - * SETS - */ - val beginTimeSets = System.currentTimeMillis() - var setForMethods: Map[String, Map[LocalVarDecl, Set[LocalVarDecl]]] = Map() - prog.methods.foreach(m => { - var vToSet: Map[LocalVarDecl, Set[LocalVarDecl]] = Map() - val allParameters: Set[LocalVarDecl] = m.formalArgs.toSet.concat(m.formalReturns.toSet) - m.formalArgs.foreach(arg => { - vToSet += (arg -> computeSet(arg,m.bodyOrAssumeFalse).intersect(allParameters)) - }) - setForMethods += (m.name -> vToSet) - - }) - - val endTimeSets = System.currentTimeMillis() - val totalTimeSets = endTimeSets - beginTimeSets - println("Time for Set analysis: " + totalTimeSets + "ms") - /* - val beginTimeSets = System.currentTimeMillis() - var vToSet: Map[LocalVarDecl,Set[LocalVarDecl]] = Map() - vars.foreach(v => { - vToSet += (v.asInstanceOf[LocalVarDecl] -> (computeSet(v,blk).intersect(vars.map(d => d.asInstanceOf[LocalVarDecl])))) - }) - val endTimeSets = System.currentTimeMillis() - val totalTimeSets = endTimeSets - beginTimeSets - - - println("Time for Set analysis: " + totalTimeSets + "ms") - - */ - - - /** - * GRAPHS - */ - - val beginTimeGraphs = System.currentTimeMillis() - val graph_analysis : VarAnalysisGraph = VarAnalysisGraph(prog, reportError) - var graphForMethods: Map[String, Graph[LocalVarDecl,DefaultEdge]] = Map[String, Graph[LocalVarDecl,DefaultEdge]]() - prog.methods.foreach(m => { - graphForMethods += (m.name -> computeGraph(graph_analysis, m.formalArgs.toSet.concat(m.formalReturns.toSet), m.bodyOrAssumeFalse)) - }) - - val endTimeGraphs = System.currentTimeMillis() - val totalTimeGraphs = endTimeGraphs - beginTimeGraphs - println("Time for Graph analysis: " + totalTimeGraphs + "ms") - println("--------------------------------------------------------------------------") - /* - val beginTimeGraphs = System.currentTimeMillis() - val graph_analysis: VarAnalysisGraph = VarAnalysisGraph(prog, reportError) - val (graph: Graph[LocalVarDecl,DefaultEdge],_) = computeGraph(graph_analysis, vars,blk,prog, reportError) - val endTimeGraphs = System.currentTimeMillis() - val totalTimeGraphs = endTimeGraphs - beginTimeGraphs - - println("Time for Graph analysis: " + totalTimeGraphs + "ms") - println("--------------------------------------------------------------------------") - - */ - - - /** compare for each variable */ - graphForMethods.foreach(mg => { - val methodname = mg._1 - val graph = mg._2 - - val set = setForMethods(methodname) - - prog.findMethod(methodname).formalArgs.foreach(arg => { - val out_edges = graph.outgoingEdgesOf(graph_analysis.createInitialVertex(arg)).asScala.toSet - - var graph_vars: Set[LocalVarDecl] = Set() - out_edges.foreach(e => { - graph_vars += graph.getEdgeTarget(e) - }) - - val set_vars: Set[LocalVarDecl] = set(arg) - - if ((graph_vars - arg).equals(set_vars - arg)) { - //println("SET AND GRAPH OF " + arg + " EQUAL") - } else { - println("SET AND GRAPH OF " + arg + " NOT EQUAL") - println("set: " + set_vars) - println("graph: " + graph_vars) - println(graph_analysis.createDOT(graph)) - println("method:\n" + methodname) - } - }) - }) - /* - vars.foreach(v => { - val out_edges = graph.outgoingEdgesOf(graph_analysis.createInitialVertex(v.asInstanceOf[LocalVarDecl])).asScala.toSet - - var graph_vars: Set[LocalVarDecl] = Set() - out_edges.foreach(e => { - graph_vars += graph.getEdgeTarget(e) - }) - - val set_vars: Set[LocalVarDecl] = vToSet(v.asInstanceOf[LocalVarDecl]) - - if ((graph_vars - v.asInstanceOf[LocalVarDecl]).equals(set_vars - v.asInstanceOf[LocalVarDecl])) { - println("SET AND GRAPH OF " + v + " EQUAL") - } else { - println("SET AND GRAPH OF " + v + " NOT EQUAL") - println("set: " + set_vars) - println("graph: " + graph_vars) - println(graph_analysis.createDOT(graph)) - println("method:\n" + m) - } - - }) - */ - } -} diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala deleted file mode 100644 index 68b6a2224..000000000 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraph.scala +++ /dev/null @@ -1,692 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2011-2024 ETH Zurich. - -package viper.silver.plugin.standard.reasoning.analysis - -import org.jgrapht.Graph -import org.jgrapht.graph.{AbstractBaseGraph, DefaultDirectedGraph, DefaultEdge} -import viper.silver.ast.{AccessPredicate, Apply, Assert, Assume, BinExp, CurrentPerm, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, Fold, ForPerm, FuncApp, Goto, If, Inhale, Int, Label, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Package, Program, Ref, Seqn, Stmt, UnExp, Unfold, While} -import viper.silver.plugin.standard.reasoning.{ExistentialElim, FlowAnnotation, Heap, OldCall, UniversalIntro, Var} -import viper.silver.verifier.{AbstractError, ConsistencyError} - -import java.io.StringWriter -import scala.jdk.CollectionConverters._ - - -case class VarAnalysisGraph(prog: Program, - reportErrorWithMsg: AbstractError => Unit) { - - val prefix: String = ".init_" - - val heap_vertex: LocalVarDecl = LocalVarDecl(".heap", Ref)() - - - /** execute the information flow analysis with graphs. - * When executed on the universal introduction statement the tainted variables are simply the quantified variables */ - def executeTaintedGraphAnalysis(tainted: Set[LocalVarDecl], blk: Seqn, allVertices: Map[LocalVarDecl, LocalVarDecl], u: UniversalIntro): Unit = { - - val graph = compute_graph(blk, allVertices) - - var noEdges: Boolean = true - var badEdges = Set[DefaultEdge]() - tainted.foreach(v => { - if (graph.edgesOf(createInitialVertex(v)).size() > 1) { - badEdges = badEdges ++ graph.edgesOf(createInitialVertex(v)).asScala.toSet[DefaultEdge] - noEdges = false - } - }) - if (!noEdges) { - var tainted_vars: Set[LocalVarDecl] = Set() - badEdges.foreach(e => { - val target = graph.getEdgeTarget(e) - if (!tainted.contains(target)) { - tainted_vars = tainted_vars + graph.getEdgeTarget(e) - } - }) - val tainted_vars_sorted: List[LocalVarDecl] = tainted_vars.toList.sortWith(_.name < _.name) - val problem_vars: String = tainted_vars_sorted.mkString(", ") - reportErrorWithMsg(ConsistencyError("Universal introduction variable might have been assigned to variable " + problem_vars + ", defined outside of the block", u.pos)) - } - } - - - /** - * Creates the Vertex that represents the initial value of the variable before the statement is executed - * @param variable Variable for which we want to create the Vertex which represents the initial value of the variable - * @return a LocalVariableDeclaration - */ - def createInitialVertex(variable:LocalVarDecl): LocalVarDecl = { - LocalVarDecl(prefix + variable.name, variable.typ)(variable.pos) - } - - /** - * creates a graph with no edges and only the vertices - * @param vertices represent the variables that are in scope - * @return an graph with only vertices - */ - def createEmptyGraph(vertices: Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl, DefaultEdge] = { - val graph: Graph[LocalVarDecl,DefaultEdge] = new DefaultDirectedGraph[LocalVarDecl,DefaultEdge](classOf[DefaultEdge]) - for ((v,v_init)<-vertices) { - graph.addVertex(v_init) - graph.addVertex(v) - } - graph - } - - - /** - * create a Graph that contains all the vertices with an edge from edge vertex representing the initial value of the variable to the 'end'-value of the variable - * @param vertices represent the variables that are in scope - * @return an identity graph - */ - private def createIdentityGraph(vertices: Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl, DefaultEdge] = { - val graph: Graph[LocalVarDecl, DefaultEdge] = createEmptyGraph(vertices) - for ((v,v_init)<-vertices) { - graph.addEdge(v_init, v) - } - graph - } - - /** - * add Edges from the vertices representing the initial value to the vertices representing its 'end'-values if they have no incoming edge yet - * @param graph existing graph - * @param vertices the vertices representing variables which should be checked - * @return graph - */ - private def addIdentityEdges(graph: Graph[LocalVarDecl,DefaultEdge], vertices:Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl, DefaultEdge] = { - for ((v,v_init)<-vertices) { - if (graph.incomingEdgesOf(v).isEmpty) { - graph.addEdge(v_init, v, new DefaultEdge) - } - } - graph - } - - /** - * for debugging purposes - * @param graph graph that should be translated to DOT-language - * @return String that is the graph in DOT-language - * - */ - def createDOT(graph: Graph[LocalVarDecl, DefaultEdge]): String = { - val writer: StringWriter = new StringWriter() - writer.write("strict digraph G {\n") - graph.vertexSet().forEach(v => { - writer.write(" " + v.name.replace(".","") + ";\n") - }) - graph.edgeSet().forEach(e => { - writer.write(" " + graph.getEdgeSource(e).name.replace(".","") + " -> " + graph.getEdgeTarget(e).name.replace(".","") + ";\n") - }) - writer.write("}\n") - writer.toString - } - - /** - * returns all the variables inside an expression - * @param graph existing graph - * @param exp expressions from which all variables should be returned - * @return set of Variable declarations - */ - private def getVarsFromExpr(graph: Graph[LocalVarDecl, DefaultEdge], exp: Exp): Set[LocalVarDecl] = { - val vars: Set[LocalVarDecl] = Set() - exp match { - case l@LocalVar(_, _) => - var l_decl: LocalVarDecl = LocalVarDecl("", Int)() - graph.vertexSet().forEach(v => if (v.name == l.name) { - l_decl = v - }) - if (l_decl.name.isEmpty) { - l_decl = LocalVarDecl(l.name, l.typ)() - } - vars + l_decl - - case BinExp(exp1, exp2) => - getVarsFromExpr(graph, exp1) ++ getVarsFromExpr(graph, exp2) - - case UnExp(exp) => - getVarsFromExpr(graph, exp) - - case FuncApp(_, exps) => - var allVars = vars - if (!vars.contains(heap_vertex)) { - allVars += heap_vertex - } - exps.foreach(e => { - val exp_vars = getVarsFromExpr(graph, e) - exp_vars.foreach(v => { - if (v.typ != Ref) { - allVars += v - } - }) - }) - allVars - - case DomainFuncApp(_, exps, _) => - var allVars = vars - exps.foreach(e => { - val exp_vars = getVarsFromExpr(graph, e) - exp_vars.foreach(v => { - allVars += v - }) - }) - allVars - - case _: ForPerm | _: CurrentPerm => - if (!vars.contains(heap_vertex)) { - vars + heap_vertex - } else { - vars - } - - case FieldAccess(v, _) => - val allVars = vars ++ getVarsFromExpr(graph,v) - if(!allVars.contains(heap_vertex)) - allVars + heap_vertex - else - allVars - - case AccessPredicate(access, _) => - /** Should only be the case in e.g.an inhale or an exhale statement */ - var allVars = vars - val access_vars = getVarsFromExpr(graph, access) - access_vars.foreach(v => { - allVars += v - }) - allVars - - case _ => Set() - } - } - - /** - * returns a shallow copy of graph instance, neither Vertices nor Edges are cloned - * @param graph graph that should be copied. - * @return copied graph - */ - def copyGraph(graph: Graph[LocalVarDecl, DefaultEdge]): Graph[LocalVarDecl, DefaultEdge] = { - graph.asInstanceOf[AbstractBaseGraph[LocalVarDecl, DefaultEdge]].clone().asInstanceOf[DefaultDirectedGraph[LocalVarDecl, DefaultEdge]] - } - - /** - * takes two graphs and returns a new graph containing the union of the edges of both input graphs. Both graphs should contain the same vertices! - * @param graph1 - * @param graph2 - * @return graph - */ - private def unionEdges(graph1: Graph[LocalVarDecl, DefaultEdge], graph2: Graph[LocalVarDecl, DefaultEdge]): Graph[LocalVarDecl,DefaultEdge] = { - val new_graph = copyGraph(graph1) - if (graph1.vertexSet().equals(graph2.vertexSet())) { - for (e2: DefaultEdge <- graph2.edgeSet().asScala.toSet) { - if (!new_graph.containsEdge(e2)) { - val src = graph2.getEdgeSource(e2) - val trgt = graph2.getEdgeTarget(e2) - new_graph.addEdge(src, trgt, e2) - } - } - } else { - throw new AssertionError(s"cannot union edges since graphs have different vertex sets") - } - new_graph - } - - /** - * merges two graphs. Meaning: we create a new graph with all the init vertices from graph one and all 'end' vertices from graph two. - * We assume that all 'end' vertices from graph1 can be matched with an init vertex from graph2. E.g. v = .init_v - * We then add an edge from a to b if there is a path from a to b. - * @param graph1 - * @param graph2 - * @param vertices - * @return merged graph - */ - private def mergeGraphs(graph1: Graph[LocalVarDecl, DefaultEdge], graph2: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl, LocalVarDecl]): Graph[LocalVarDecl,DefaultEdge] = { - val new_graph = createEmptyGraph(vertices) - for (e1: DefaultEdge <- graph1.edgeSet().asScala.toSet) { - val src = graph1.getEdgeSource(e1) - val trgt = graph1.getEdgeTarget(e1) - val init_trgt = vertices.get(trgt) - if (init_trgt.isDefined) { - for (e2: DefaultEdge <- graph2.outgoingEdgesOf(init_trgt.get).asScala.toSet) { - new_graph.addEdge(src, graph2.getEdgeTarget(e2), new DefaultEdge) - } - } else { - throw new AssertionError(s"Vertex not found for declaration $trgt") - } - } - new_graph - } - - - /** creates a graph based on the statement - * edge is influenced by relation: source influences target - * vertices are all variables in scope*/ - def compute_graph(stmt: Stmt, vertices: Map[LocalVarDecl,LocalVarDecl]): Graph[LocalVarDecl,DefaultEdge] = { - stmt match { - case Seqn(ss, scopedSeqnDeclarations) => - var allVertices: Map[LocalVarDecl,LocalVarDecl] = vertices - for (d <- scopedSeqnDeclarations) { - d match { - case decl: LocalVarDecl => - val d_init = createInitialVertex(decl) - allVertices += (decl -> d_init) - } - } - var new_graph: Graph[LocalVarDecl, DefaultEdge] = createIdentityGraph(allVertices) - for (s <- ss) { - val comp_graph = compute_graph(s, allVertices) - new_graph = mergeGraphs(new_graph, comp_graph, allVertices) - } - - val final_graph: Graph[LocalVarDecl, DefaultEdge] = createEmptyGraph(vertices) - new_graph.edgeSet().forEach(e => { - val source: LocalVarDecl = new_graph.getEdgeSource(e) - val target: LocalVarDecl = new_graph.getEdgeTarget(e) - if (final_graph.containsVertex(source) && final_graph.containsVertex(target)) { - final_graph.addEdge(source, target, e) - } - }) - final_graph - - case If(cond, thn, els) => - val id_graph = createIdentityGraph(vertices) - val expr_vars = getVarsFromExpr(id_graph, cond) - val cond_graph = copyGraph(id_graph) - val thn_graph = compute_graph(thn, vertices) - val els_graph = compute_graph(els, vertices) - val writtenToThn = getModifiedVars(vertices, thn).getOrElse(Set.empty) - val writtenToEls = getModifiedVars(vertices, els).getOrElse(Set.empty) - val allWrittenTo = writtenToThn ++ writtenToEls - for (w <- allWrittenTo) { - if (cond_graph.containsVertex(w)) { - for (v <- expr_vars) { - val v_init = vertices(v) - cond_graph.addEdge(v_init, w, new DefaultEdge) - } - } - } - writtenToThn.intersect(writtenToEls).foreach(v => { - cond_graph.removeEdge(vertices(v),v) - }) - val thn_els_graph = unionEdges(thn_graph, els_graph) - unionEdges(cond_graph, thn_els_graph) - - case While(cond, _, body) => - /** analyse one iteration of the while loop */ - val one_iter_graph: Graph[LocalVarDecl, DefaultEdge] = compute_graph(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), vertices) - var edges_equal: Boolean = false - var merge_graph = copyGraph(one_iter_graph) - while(!edges_equal) { - val last_iter_graph = copyGraph(merge_graph) - merge_graph = mergeGraphs(merge_graph, one_iter_graph, vertices) - val equal_size: Boolean = last_iter_graph.edgeSet().size().equals(merge_graph.edgeSet().size()) - if (equal_size && last_iter_graph.vertexSet().equals(merge_graph.vertexSet())) { - for (e1: DefaultEdge <- last_iter_graph.edgeSet().asScala.toSet) { - if (merge_graph.getEdge(last_iter_graph.getEdgeSource(e1), last_iter_graph.getEdgeTarget(e1)) == null) { - edges_equal = false - } else { - edges_equal = true - } - } - } - } - merge_graph - - case LocalVarAssign(lhs,rhs) => - var new_graph: Graph[LocalVarDecl,DefaultEdge] = createEmptyGraph(vertices) - val rhs_vars = getVarsFromExpr(new_graph, rhs) - val lhs_decl: LocalVarDecl = LocalVarDecl(lhs.name,lhs.typ)(lhs.pos) - for (v <- rhs_vars) { - /** if the variable on the right hand side is a field access */ - if (v.equals(heap_vertex)) { - val heap_init = vertices(heap_vertex) - new_graph.addEdge(heap_init, lhs_decl, new DefaultEdge) - } else { - val v_init = vertices(v) - new_graph.addEdge(v_init, lhs_decl, new DefaultEdge) - } - } - /** Since variables that are not assigned to should have an edge from their initial value to their 'end'-value */ - val vert_wout_lhs = vertices - lhs_decl - new_graph = addIdentityEdges(new_graph, vert_wout_lhs) - new_graph - - case Inhale(exp) => - val id_graph = createIdentityGraph(vertices) - expInfluencesAllVertices(exp, id_graph, vertices) - - /** same as inhale */ - case Assume(exp) => - val id_graph = createIdentityGraph(vertices) - expInfluencesAllVertices(exp, id_graph, vertices) - - case Exhale(exp) => - val id_graph = createIdentityGraph(vertices) - if (exp.isPure) { - id_graph - } else { - val exhale_vars = getVarsFromExpr(id_graph, exp) - exhale_vars.foreach(v => { - if (v.typ == Ref) { - val init_v = createInitialVertex(v) - id_graph.addEdge(init_v, heap_vertex, new DefaultEdge) - } - }) - id_graph - } - - case Assert(_) => createIdentityGraph(vertices) - - case Label(_, _) => createIdentityGraph(vertices) - - case MethodCall(methodName, args, targets) => - val met = prog.findMethod(methodName) - val methodcall_graph = createEmptyGraph(vertices) - createInfluencedByGraph(methodcall_graph,vertices,args,targets,met.formalArgs, met.formalReturns,met.posts) - - case FieldAssign(_, rhs) => - val id_graph = createIdentityGraph(vertices) - val rhs_vars = getVarsFromExpr(id_graph, rhs) - rhs_vars.foreach(v => { - /** Edge from .init_heap to heap does not have to be added since it exists anyways */ - if (v.equals(heap_vertex)) { - id_graph - } else { - val v_init = createInitialVertex(v) - id_graph.addEdge(v_init, heap_vertex, new DefaultEdge) - } - }) - id_graph - - /** TODO: technically not implemented correctly */ - case ExistentialElim(_,_,_) => createIdentityGraph(vertices) - - case UniversalIntro(varList,_,_,_,blk) => - val new_vertices: Map[LocalVarDecl, LocalVarDecl] = vertices ++ varList.map(v => (v -> createInitialVertex(v))) - val new_graph = compute_graph(blk,new_vertices) - varList.foreach(v => { - new_graph.removeVertex(v) - new_graph.removeVertex(new_vertices(v)) - }) - new_graph - - case Fold(acc) => - val id_graph = createIdentityGraph(vertices) - val vars = getVarsFromExpr(id_graph, acc) - vars.foreach(v => { - id_graph.addEdge(vertices(v), heap_vertex, new DefaultEdge) - }) - id_graph - - case Unfold(acc) => - val id_graph = createIdentityGraph(vertices) - val vars = getVarsFromExpr(id_graph, acc) - vars.foreach(v => { - id_graph.addEdge(vertices(v), heap_vertex, new DefaultEdge) - }) - id_graph - - case Apply(exp) => - val id_graph = createIdentityGraph(vertices) - val vars = getVarsFromExpr(id_graph, exp) - vars.foreach(v => { - id_graph.addEdge(vertices(v), heap_vertex, new DefaultEdge) - }) - id_graph - - case Package(wand, _) => - val id_graph = createIdentityGraph(vertices) - val vars = getVarsFromExpr(id_graph, wand) - vars.foreach(v => { - id_graph.addEdge(vertices(v), heap_vertex, new DefaultEdge) - }) - id_graph - - case g: Goto => - reportErrorWithMsg(ConsistencyError(s"$g is an undefined statement for the modular information flow analysis", g.pos)) - createEmptyGraph(vertices) - - case OldCall(methodName, args, targets, _) => - val met = prog.findMethod(methodName) - val methodcall_graph: Graph[LocalVarDecl, DefaultEdge] = createEmptyGraph(vertices) - - createInfluencedByGraph(methodcall_graph, vertices, args, targets, met.formalArgs, met.formalReturns, met.posts) - - case _ => - reportErrorWithMsg(ConsistencyError(s"$stmt is an undefined statement for the modular information flow analysis", stmt.pos)) - createEmptyGraph(vertices) - } - } - - /** - * creates an edge between every variable in the expression to every variable that is in scope and returns resulting graph - */ - private def expInfluencesAllVertices(exp:Exp, graph: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl,LocalVarDecl]) : Graph[LocalVarDecl, DefaultEdge] = { - val id_graph = createIdentityGraph(vertices) - val vars = getVarsFromExpr(graph, exp) - vars.foreach(v => { - val init_v = vertices(v) - vertices.keySet.foreach(k => { - id_graph.addEdge(init_v, k, new DefaultEdge) - }) - }) - id_graph - } - - /** creates graph for methodcall and oldcall. maps expressions passed to methods to the method arguments, computes the graph based on the flow annotation, - * and finally maps the return variables to the variables that the method is assigned to. */ - private def createInfluencedByGraph(graph: Graph[LocalVarDecl, DefaultEdge], vertices: Map[LocalVarDecl,LocalVarDecl], arg_names: Seq[Exp], ret_names: Seq[LocalVar], method_arg_names: Seq[LocalVarDecl], method_ret_names: Seq[LocalVarDecl], posts: Seq[Exp]): Graph[LocalVarDecl, DefaultEdge] = { - /** set of all target variables that have not been included in the influenced by expression up until now */ - var retSet: Set[LocalVarDecl] = method_ret_names.toSet + heap_vertex - - var methodcall_graph = copyGraph(graph) - val method_arg_names_incl_heap = method_arg_names ++ Seq(heap_vertex) - - /** create .arg_ declaration for each argument */ - var method_args: Map[LocalVarDecl, LocalVarDecl] = Map.empty - var method_arg_counter: Int = 0 - method_arg_names_incl_heap.foreach(method_arg => { - method_args += (method_arg -> LocalVarDecl(".arg" + method_arg_counter, method_arg.typ)(method_arg.pos)) - method_arg_counter += 1 - }) - - /** create .ret_ declaration for each return variable */ - var method_rets: Map[LocalVarDecl, LocalVarDecl] = Map() - var method_ret_counter: Int = 0 - retSet.foreach(method_ret => { - method_rets += (method_ret -> LocalVarDecl(".ret" + method_ret_counter, method_ret.typ)(method_ret.pos)) - method_ret_counter += 1 - }) - - /** contains all variables that are passed to the method */ - var total_arg_decls: Set[LocalVarDecl] = Set(heap_vertex) - - /** add edges from method arguments to .arg variables */ - (arg_names zip method_arg_names).foreach(arg => { - /** extract all variables in expressions that are added to the method */ - val arg_decls: Set[LocalVarDecl] = getVarsFromExpr(graph, arg._1) - total_arg_decls ++= arg_decls - arg_decls.foreach(arg_decl => { - if (!methodcall_graph.containsVertex(vertices(arg_decl))) { - methodcall_graph.addVertex(vertices(arg_decl)) - } - if (!methodcall_graph.containsVertex(method_args(arg._2))) { - methodcall_graph.addVertex(method_args(arg._2)) - } - /** add edge from .init variable to .arg variable */ - methodcall_graph.addEdge(vertices(arg_decl), method_args(arg._2)) - }) - }) - - /** add heap and corresponding .arg variable as method argument */ - if(!methodcall_graph.containsVertex(heap_vertex)) { - methodcall_graph.addVertex(vertices(heap_vertex)) - } - if(!methodcall_graph.containsVertex(method_args(heap_vertex))) { - methodcall_graph.addVertex(method_args(heap_vertex)) - } - methodcall_graph.addEdge(vertices(heap_vertex),method_args(heap_vertex)) - - - /** need to add the edges from the influenced by expression */ - posts.foreach { - case FlowAnnotation(returned, arguments) => - - /** returned has to be instance of LocalVar */ - val returned_var: LocalVar = returned match { - case v: Var => v.decl - case _: Heap => heap_vertex.localVar - } - /** create LocalVarDecl such that it can be added in the graph */ - val return_decl = LocalVarDecl(returned_var.name, returned_var.typ)(returned_var.pos) - retSet -= return_decl - - if (!methodcall_graph.containsVertex(method_rets(return_decl))) { - methodcall_graph.addVertex(method_rets(return_decl)) - } - - arguments.foreach(argument => { - /** argument has to be instance of LocalVar */ - val argument_var: LocalVar = argument match { - case v: Var => v.decl - case _: Heap => heap_vertex.localVar - } - val argument_decl = LocalVarDecl(argument_var.name, argument_var.typ)(argument_var.pos) - /** get corresponding .arg variable and add edge from .arg to .ret vertex */ - val prov_decl = method_args(argument_decl) - methodcall_graph.addEdge(prov_decl, method_rets(return_decl), new DefaultEdge) - }) - - case _ => - } - - /** now need to add to graph the edges from the method return variables to the target variables */ - val targets_decl: Seq[LocalVarDecl] = ret_names.map(t => { - graph.vertexSet().asScala.filter(lvd => lvd.name == t.name).head - }) ++ Seq(heap_vertex) - ((method_ret_names ++ Seq(heap_vertex)) zip targets_decl).foreach(ret => { - if (!methodcall_graph.containsVertex(ret._2)) { - methodcall_graph.addVertex(ret._2) - } - if (!methodcall_graph.containsVertex(method_rets(ret._1))) { - methodcall_graph.addVertex(method_rets(ret._1)) - } - /** add edge from .ret variable to target variable */ - methodcall_graph.addEdge(method_rets(ret._1), ret._2) - }) - - - /** add edges from all method argument to each return variable that wasn't mentioned in the influenced by statement */ - retSet.foreach(r => { - method_arg_names_incl_heap.foreach(a => { - if (!methodcall_graph.containsVertex(method_args(a))) { - methodcall_graph.addVertex(method_args(a)) - } - if (!methodcall_graph.containsVertex(method_rets(r))) { - methodcall_graph.addVertex(method_rets(r)) - } - methodcall_graph.addEdge(method_args(a), method_rets(r), new DefaultEdge) - }) - }) - - - var copy_arg_graph = copyGraph(methodcall_graph) - - /** remove edge from .ret_ vertex to the final vertex */ - for (elem <- targets_decl) { - /** get all edges from target variables to .ret variables */ - copy_arg_graph.incomingEdgesOf(elem).forEach(inc_edge => { - //should only be one edge - val ret_vert = methodcall_graph.getEdgeSource(inc_edge) - /** get edges from .ret variable to .arg variable */ - copy_arg_graph.incomingEdgesOf(ret_vert).forEach(ret_inc_e => { - val arg_vert = methodcall_graph.getEdgeSource(ret_inc_e) - /** add edge from .arg variable to target variable */ - methodcall_graph.addEdge(arg_vert, elem) - }) - /** remove .ret variables */ - methodcall_graph.removeVertex(ret_vert) - }) - } - - /** remove rest of .ret variable incase no assigment */ - method_rets.values.foreach(ret_vert => { - methodcall_graph.removeVertex(ret_vert) - }) - - - - /** remove edge from the .arg_ to the .init vertex */ - copy_arg_graph = copyGraph(methodcall_graph) - /** go through .init variables */ - for (elem <- total_arg_decls + heap_vertex) { - /** go through all outgoing edges of .init variable */ - copy_arg_graph.outgoingEdgesOf(vertices(elem)).forEach(out_edge => { - /** get the .arg variable that edge leads to */ - val arg_vert = methodcall_graph.getEdgeTarget(out_edge) - /** get edges from .arg variable to the target variable */ - copy_arg_graph.outgoingEdgesOf(arg_vert).forEach(arg_out_e => { - val final_vert = methodcall_graph.getEdgeTarget(arg_out_e) - /** create edge from .init variable to target variable */ - methodcall_graph.addEdge(vertices(elem), final_vert) - }) - methodcall_graph.removeVertex(arg_vert) - }) - } - - /** Since variables that are not assigned to should have an edge from their initial value to their 'end'-value */ - val vert_wout_lhs = vertices.removedAll(targets_decl) - methodcall_graph = addIdentityEdges(methodcall_graph, vert_wout_lhs) - methodcall_graph - } - - - /** - * get the variables that were modified by the statement stmt - */ - def getModifiedVars(vertices: Map[LocalVarDecl,LocalVarDecl], stmt: Stmt): Option[Set[LocalVarDecl]] = { - var output: Option[Set[LocalVarDecl]] = None - stmt match { - case Seqn(ss, _) => - for (s <- ss) { - output match { - case None => output = getModifiedVars(vertices, s) - case Some(v) => output = Some(v ++ getModifiedVars(vertices, s).getOrElse(Set[LocalVarDecl]())) - } - } - output - - case LocalVarAssign(lhs, _) => - var res: Option[Set[LocalVarDecl]] = None - for (vs <- vertices) { - if (vs._1.name == lhs.name) { - res = Some(Set(vs._1)) - } else { - /** This is the case if the variable is in scope in e.g. a then or an else block. */ - res = Some(Set(LocalVarDecl(lhs.name, lhs.typ)(lhs.pos))) - } - } - res - - case If(_, thn, els) => - val writtenThn = getModifiedVars(vertices, thn) - val writtenEls = getModifiedVars(vertices, els) - (writtenThn, writtenEls) match { - case (None, None) => None - case (Some(_), None) => writtenThn - case (None, Some(_)) => writtenEls - case (Some(t), Some(e)) => Some(t ++ e) - } - - case While(_, _, body) => - getModifiedVars(vertices, body) - - case MethodCall(_, _, _) => None - case Inhale(_) => None - case Assume(_) => None - case Label(_, _) => None - case _ => None - } - } -} diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index cbb490800..979d84871 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -6,129 +6,224 @@ package viper.silver.plugin.standard.reasoning.analysis -import viper.silver.ast.{AccessPredicate, Apply, Assert, Assume, BinExp, CurrentPerm, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, Fold, ForPerm, FuncApp, Goto, If, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Package, Program, Ref, Seqn, Stmt, UnExp, Unfold, While} +import viper.silver.ast.utility.Expressions +import viper.silver.ast.{AccessPredicate, Apply, Assert, Assume, BinExp, CurrentPerm, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, Fold, ForPerm, FuncApp, Goto, If, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, Method, MethodCall, Package, Position, Program, Quasihavoc, Quasihavocall, Ref, Scope, Seqn, Stmt, UnExp, Unfold, While} import viper.silver.plugin.standard.reasoning.{ExistentialElim, FlowAnnotation, FlowVar, Heap, OldCall, UniversalIntro, Var} import viper.silver.verifier.{AbstractError, ConsistencyError} +import scala.collection.mutable case class VarAnalysisGraphMap(prog: Program, logger: ch.qos.logback.classic.Logger, reportErrorWithMsg: AbstractError => Unit) { - val prefix: String = ".init_" - val heap_vertex: LocalVarDecl = LocalVarDecl(".heap", Ref)() + // Maps all influences for a given variable private type GraphMap = Map[LocalVarDecl, Set[LocalVarDecl]] + // Maps all influences for a statement at a given position + private type AssumeAnalysis = mutable.Map[Position, Set[LocalVarDecl]] + + private val heapVertex: LocalVarDecl = LocalVarDecl(".heap", Ref)() + + // Storage for method analysis + private val methodAnalysisMap: mutable.Map[Method, GraphMap] = mutable.Map() + private val methodAssumeAnalysisMap: mutable.Map[Method, AssumeAnalysis] = mutable.Map() + private val methodAnalysisStarted: mutable.ListBuffer[Method] = mutable.ListBuffer() /** execute the information flow analysis with graphs. * When executed on the universal introduction statement the tainted variables are simply the quantified variables */ def executeTaintedGraphAnalysis(allVars: Set[LocalVarDecl], tainted: Set[LocalVarDecl], blk: Seqn, volatileVars: Set[LocalVarDecl], u: UniversalIntro): Unit = { // Build initial graph where every variable and the heap influences itself - val initialGraph = allVars.map(k => k -> Set(k)).toMap + (heap_vertex -> Set(heap_vertex)) - - val graph = computeInfluenceMap(blk, initialGraph, Set(), tainted) + val initialGraph = allVars.map(k => k -> Set(k)).toMap + (heapVertex -> Set(heapVertex)) + val assumeAnalysis: AssumeAnalysis = mutable.Map() + val graph = computeInfluenceMap(blk, initialGraph, Set())(assumeAnalysis) val problems = volatileVars.filter(v => graph(v).intersect(tainted).nonEmpty) - if(problems.nonEmpty) { reportErrorWithMsg(ConsistencyError("Universal introduction variable might have been assigned to variable " + problems + ", used in the quantified implication", u.pos)) } + assumeAnalysis.foreach(v => { + val assumeVars = v._2.intersect(tainted) + if (assumeVars.nonEmpty) { + val vars = assumeVars.map(v => s"'${v.name}'").mkString(", ") + val err = if (assumeVars.size == 1) { + "variable " + vars + } else { + "variables " + vars + } + reportErrorWithMsg(ConsistencyError(s"Tainted $err might influence assume statement", v._1)) + } + }) + } + + /** + * Executes the taint analysis for a method and stores the resulting graph map in methodAnalysisMap and the resulting + * assume analysis in the methodAssumeAnalysisMap. + * + * If a recursive method is encountered, we fall back to the specified influences or a over approximation for the graph map + * and an over approximation for the assume analysis + */ + private def executeTaintedGraphMethodAnalysis(method: Method): Unit = { + if(methodAnalysisStarted.contains(method) || method.body.isEmpty) { + if(methodAnalysisStarted.contains(method)) { + logger.warn(s"Taint analysis does not support recursive method calls. Falling back to specified influences. (${method.name} ${method.pos})") + } + methodAnalysisMap.put(method, getDefaultMethodInfluences(method)) + methodAssumeAnalysisMap.put(method, overApproximateAssumeInfluences(method)) + } else { + methodAnalysisStarted.addOne(method) + val varDecls = method.formalReturns.toSet ++ method.formalArgs.toSet + heapVertex + val initialGraph = varDecls.map(k => k -> Set(k)).toMap + (heapVertex -> Set(heapVertex)) + + val stmt = Seqn(method.body.get.ss, method.body.get.scopedSeqnDeclarations.filter({ + case l: LocalVarDecl => !varDecls.contains(l) + case _ => true + }))(method.body.get.pos, method.body.get.info, method.body.get.errT) + + val assumeAnalysis: AssumeAnalysis = mutable.Map() + val map = computeInfluenceMap(stmt, initialGraph, Set())(assumeAnalysis) + + if(!methodAnalysisMap.contains(method)) { + // Check calculated value against the provided specification if there are any + method.posts.collect({ case f: FlowAnnotation => f }).foreach(f => { + val returnVar = getLocalVarDeclFromFlowVar(f.v) + val specifiedInfluences = f.varList.map(getLocalVarDeclFromFlowVar).toSet + val calculatedInfluences = lookupVar(returnVar, map) + if (calculatedInfluences != specifiedInfluences) { + reportErrorWithMsg(ConsistencyError(s"Specified influence on return variable $returnVar differs from calculated value. Specified: $specifiedInfluences Calculated: $calculatedInfluences", f.pos)) + } + }) + + methodAnalysisMap.put(method, map) + methodAssumeAnalysisMap.put(method, assumeAnalysis) + } + } + methodAnalysisStarted -= method + } + + /** + * Fallback for the influence analysis of methods. If influences for a return variable (including the heap) are declared, they are trusted. + * Otherwise we over approximate by assuming every method arg (including the heap) influences the return variable. + */ + private def getDefaultMethodInfluences(method: Method): GraphMap = { + val retSet = method.formalReturns.toSet + heapVertex + val allMethodArgsSet = method.formalArgs.toSet + heapVertex + + val annotationInfluences = method.posts.collect({ case FlowAnnotation(returned, arguments) => (returned, arguments) }).map(t => { + getLocalVarDeclFromFlowVar(t._1) -> t._2.map(getLocalVarDeclFromFlowVar).toSet + }).toMap + /** influence all return variables, not mentioned by annotation, by every method argument */ + val otherInfluences = (retSet -- annotationInfluences.keySet).map(retVar => { + retVar -> allMethodArgsSet + }) + annotationInfluences ++ otherInfluences + } + + /** + * Over approximates the assume analysis, by assuming that the method's arguments influence every inhale statement + * that occurs in this method or in any transitively called method. + */ + private def overApproximateAssumeInfluences(method: Method): AssumeAnalysis = { + var allCalledMethods: Set[Method] = Set(method) + var nextSet: Set[Method] = allCalledMethods ++ getAllCalledMethods(method) + + while (allCalledMethods != nextSet) { + allCalledMethods = nextSet + nextSet = allCalledMethods ++ allCalledMethods.flatMap(getAllCalledMethods) + } + + val allInhales = allCalledMethods.flatMap(met => met.deepCollectInBody({ case a: Inhale => a })) + val assumeAnalysis: AssumeAnalysis = mutable.Map() + assumeAnalysis.addAll(allInhales.map(i => i.pos -> method.formalArgs.toSet)) } + private def getAllCalledMethods(method: Method): Set[Method] = method.deepCollectInBody({ case a: MethodCall => a }).map(m => prog.findMethod(m.methodName)).toSet - private def computeInfluenceMap(stmt: Stmt, graphMap: GraphMap, assumeInfluences: Set[LocalVarDecl], tainted: Set[LocalVarDecl]): GraphMap = { - logger.warn("Statement: " + stmt) + private def computeInfluenceMap(stmt: Stmt, graphMap: GraphMap, pathInfluencingVariables: Set[LocalVarDecl])(implicit assumeAnalysis: AssumeAnalysis): GraphMap = { stmt match { - // TODO Question: do scopedSeqnDeclarations overwrite existing variables in the subgraphs?? - case Seqn(ss, scopedSeqnDeclarations) => - // TODO Question: Idea is, I overwrite all scoped declarations with a variable that only influences itseld - // And afterwards restore the influence before the scoped block, is this correct? - val declarations = scopedSeqnDeclarations.collect({ case l: LocalVarDecl => l }) + case s: Scope => + // Temporarily add the scoped declarations to the graph and remove them afterwards + val declarations = s.scopedDecls.collect({ case l: LocalVarDecl => l }) val scopedGraph = graphMap ++ declarations.map(decl => decl -> Set(decl)).toMap - ss.foldLeft(scopedGraph) { (graph, subStmt) => computeInfluenceMap(subStmt, graph, assumeInfluences, tainted) } - // TODO Question: Havoc? - // case Havoc(_) => ? + val graph = s match { + case Seqn(ss, _) => + ss.foldLeft(scopedGraph) { (graph, subStmt) => computeInfluenceMap(subStmt, graph, pathInfluencingVariables) } + // TODO Why is OldCall a Scope statement? + case o: OldCall => + val met = prog.findMethod(o.methodName) + computeMethodInfluenceMap(graphMap, met, o.args, o.rets, pathInfluencingVariables, o.pos) + // The quantified variables of the Quasihavocall statement are ignored because they are untainted by definition + case Quasihavocall(_, lhs, _) => + val vars = lhs.map(e => getResolvedVarsFromExp(e, graphMap)).getOrElse(Set()) + graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars)) + case u: UniversalIntro => computeInfluenceMap(u.block, scopedGraph, pathInfluencingVariables) + } + graph.removedAll(declarations) case LocalVarAssign(lhs, rhs) => - val influences = getVarsFromExpr(rhs).flatMap(v => graphMap(v)) + val influences = getResolvedVarsFromExp(rhs, graphMap).flatMap(v => lookupVar(v, graphMap)) graphMap + (getLocalVarDeclFromLocalVar(lhs) -> influences) - case UniversalIntro(quantifiedVars, _, _, _, s) => computeInfluenceMap(s, graphMap ++ quantifiedVars.map(v => v -> Set()), assumeInfluences, tainted ++ quantifiedVars) case If(cond, thn, els) => - val conditionVars = getVarsFromExpr(cond) - + val conditionVars = getResolvedVarsFromExp(cond, graphMap) // For the condition influences, we only care for variables that are declared outside of the if block - val writesInIfBlocks = (getModifiedVars(thn) ++ getModifiedVars(els)).filter(v => graphMap.contains(v)); - val conditionInfluences = writesInIfBlocks.map(v => v -> (graphMap(v) ++ conditionVars.flatMap(c => graphMap(c)))).toMap + val writesInIfBlocks = (getModifiedVars(thn) ++ getModifiedVars(els)).filter(v => graphMap.contains(v)) + val conditionInfluences = writesInIfBlocks.map(v => v -> (lookupVar(v, graphMap) ++ conditionVars.flatMap(c => lookupVar(c, graphMap)))).toMap - val thn_graph = computeInfluenceMap(thn, graphMap, assumeInfluences ++ conditionVars, tainted) - val els_graph = computeInfluenceMap(els, graphMap, assumeInfluences ++ conditionVars, tainted) - thn_graph.keySet.union(els_graph.keySet).map( - v => v -> (thn_graph.getOrElse(v, Set()) ++ els_graph.getOrElse(v, Set()) ++ conditionInfluences.getOrElse(v, Set())) + val thenGraph = computeInfluenceMap(thn, graphMap, pathInfluencingVariables ++ conditionVars) + val elseGraph = computeInfluenceMap(els, graphMap, pathInfluencingVariables ++ conditionVars) + (thenGraph.keySet ++ elseGraph.keySet).map( + v => v -> (thenGraph.getOrElse(v, Set()) ++ elseGraph.getOrElse(v, Set()) ++ conditionInfluences.getOrElse(v, Set())) ).toMap case While(cond, _, body) => - // TODO Question: This seems like quite a smart way to do this, can we keep it this way? - var iteration_graph = computeInfluenceMap(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), graphMap, assumeInfluences, tainted) - var edges_equal: Boolean = false - var merge_graph = iteration_graph - var iterations = 1; - while (!edges_equal) { - iteration_graph = computeInfluenceMap(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), merge_graph, assumeInfluences, tainted) - if(iteration_graph.equals(merge_graph)) { - edges_equal = true + var iterationGraph = computeInfluenceMap(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), graphMap, pathInfluencingVariables) + var edgesEqual: Boolean = false + var mergeGraph = iterationGraph + while (!edgesEqual) { + iterationGraph = computeInfluenceMap(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), mergeGraph, pathInfluencingVariables) + if(iterationGraph.equals(mergeGraph)) { + edgesEqual = true } else { - merge_graph = iteration_graph + mergeGraph = iterationGraph } - iterations += 1; } - merge_graph - case MethodCall(methodName, args, targets) => - val met = prog.findMethod(methodName) - computeMethodInfluenceMap(graphMap, args, targets, met.formalArgs, met.formalReturns, met.posts) - - case OldCall(methodName, args, targets, _) => - val met = prog.findMethod(methodName) - computeMethodInfluenceMap(graphMap, args, targets, met.formalArgs, met.formalReturns, met.posts) - + mergeGraph + case m: MethodCall => + val met = prog.findMethod(m.methodName) + computeMethodInfluenceMap(graphMap, met, m.args, m.targets, pathInfluencingVariables, m.pos) case FieldAssign(_, rhs) => - val vars = getVarsFromExpr(rhs) - graphMap + (heap_vertex -> (graphMap(heap_vertex) ++ vars.filter(v => !v.equals(heap_vertex)).flatMap(v => graphMap(v)))) + val vars = getResolvedVarsFromExp(rhs, graphMap) + graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars.filter(v => !v.equals(heapVertex)).flatMap(v => graphMap(v)))) case Exhale(exp) => if (exp.isPure) { graphMap } else { - val vars = getVarsFromExpr(exp) - graphMap + (heap_vertex -> (graphMap(heap_vertex) ++ vars)) + val vars = getResolvedVarsFromExp(exp, graphMap) + graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars)) } case Fold(acc) => - val vars = getVarsFromExpr(acc) - graphMap + (heap_vertex -> (graphMap(heap_vertex) ++ vars)) + val vars = getResolvedVarsFromExp(acc, graphMap) + graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars)) case Unfold(acc) => - val vars = getVarsFromExpr(acc) - graphMap + (heap_vertex -> (graphMap(heap_vertex) ++ vars)) + val vars = getResolvedVarsFromExp(acc, graphMap) + graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars)) case Apply(exp) => - val vars = getVarsFromExpr(exp) - graphMap + (heap_vertex -> (graphMap(heap_vertex) ++ vars)) + val vars = getResolvedVarsFromExp(exp, graphMap) + graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars)) case Package(wand, _) => - val vars = getVarsFromExpr(wand) - graphMap + (heap_vertex -> (graphMap(heap_vertex) ++ vars)) - - + val vars = getResolvedVarsFromExp(wand, graphMap) + graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars)) + case Quasihavoc(lhs, _) => + val vars = lhs.map(e => getResolvedVarsFromExp(e, graphMap)).getOrElse(Set()) + graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars)) + case Assert(_) => graphMap + case Label(_, _) => graphMap + // Assume analysis case a: Inhale => - val assumeVars = (assumeInfluences ++ getVarsFromExpr(a.exp)).flatMap(v => graphMap(v) + v).intersect(tainted) - logger.warn("Assume vars: " + assumeVars) - if (assumeVars.nonEmpty) { - reportErrorWithMsg(ConsistencyError(s"Universal introduction variable might influence assume statement: $a ", a.pos)) - } + val assumeVars = (pathInfluencingVariables ++ getResolvedVarsFromExp(a.exp, graphMap)).flatMap(v => lookupVar(v, graphMap) + v) + assumeAnalysis.addOne(a.pos -> assumeVars) graphMap + case ExistentialElim(vars, _, exp) => graphMap ++ vars.map(v => v -> getResolvedVarsFromExp(exp, graphMap)) + // Non handled cases case a: Assume => - val assumeVars = (assumeInfluences ++ getVarsFromExpr(a.exp)).flatMap(v => graphMap(v) + v).intersect(tainted) - logger.warn("Assume vars: " + assumeVars) - if (assumeVars.nonEmpty) { - reportErrorWithMsg(ConsistencyError(s"Universal introduction variable might influence assume statement: $a ", a.pos)) - } - graphMap - case Assert(_) => graphMap - case Label(_, _) => graphMap - case e: ExistentialElim => - reportErrorWithMsg(ConsistencyError(s"$e is an undefined statement for the modular information flow analysis", e.pos)) + reportErrorWithMsg(ConsistencyError("Unexpected assume statement in the modular information flow analysis", a.pos)) graphMap case g: Goto => reportErrorWithMsg(ConsistencyError(s"$g is an undefined statement for the modular information flow analysis", g.pos)) @@ -139,87 +234,30 @@ case class VarAnalysisGraphMap(prog: Program, } } - /** - * returns all the variables inside an expression - * @param graph existing graph - * @param exp expressions from which all variables should be returned - * @return set of Variable declarations - */ - def getVarsFromExpr(exp: Exp): Set[LocalVarDecl] = { - val vars: Set[LocalVarDecl] = Set() - exp match { - case l@LocalVar(_, _) => Set(getLocalVarDeclFromLocalVar(l)) - case BinExp(exp1, exp2) => getVarsFromExpr(exp1) ++ getVarsFromExpr(exp2) - case UnExp(exp) => getVarsFromExpr(exp) - case FuncApp(_, exps) => - // TODO Question: how do we differentiate between functions & methods -> functions are side-effect free, so the heap is not needed there - // TODO Question: Couldn't we use our method influence analysis here as well as an easy way to not over approximate? - exps.flatMap(e => getVarsFromExpr(e).filter(v => v.typ != Ref)).toSet // + heap_vertex - case DomainFuncApp(_, exps, _) => - vars ++ exps.flatMap(e => getVarsFromExpr(e)) - case _: ForPerm | _: CurrentPerm => - Set(heap_vertex) - case FieldAccess(v, _) => - getVarsFromExpr(v) + heap_vertex - case AccessPredicate(access, _) => - /** Should only be the case in e.g.an inhale or an exhale statement */ - var allVars = vars - val access_vars = getVarsFromExpr(access) - access_vars.foreach(v => { - allVars += v - }) - allVars - case _ => Set() - } - } - - /** - * creates an edge between every variable in the expression to every variable that is in scope and returns resulting graph - */ - private def expInfluencesAllVertices(exp:Exp, graphMap: GraphMap) : GraphMap = { - // TODO Question: Is this correct? (used by Inhale) - val vars = getVarsFromExpr(exp) - val influences = vars.flatMap(v => graphMap(v)) - graphMap.map(f => f._1 -> influences) - } - - /** creates graph for methodcall and oldcall. maps expressions passed to methods to the method arguments, computes the graph based on the flow annotation, + /** creates graph for method call and old call. maps expressions passed to methods to the method arguments, computes the graph based on the flow annotation, * and finally maps the return variables to the variables that the method is assigned to. */ - private def computeMethodInfluenceMap(graphMap: GraphMap, argExpressions: Seq[Exp], retVars: Seq[LocalVar], methodArgVars: Seq[LocalVarDecl], methodRetVars: Seq[LocalVarDecl], posts: Seq[Exp]): GraphMap = { + private def computeMethodInfluenceMap(graphMap: GraphMap, method: Method, callArgs: Seq[Exp], callTargets: Seq[LocalVar], pathInfluencingVars: Set[LocalVarDecl], pos: Position)(implicit assumeAnalysis: AssumeAnalysis): GraphMap = { /** set of all target variables that have not been included in the influenced by expression up until now */ - val retSet: Set[LocalVarDecl] = methodRetVars.toSet + heap_vertex - - /** create .arg_ declaration for each argument */ - val methodArgExpMapping = (methodArgVars zip argExpressions).map(method_arg => { - method_arg._1 -> getVarsFromExpr(method_arg._2) - }).toMap + val methodArgExpMapping = (method.formalArgs zip callArgs).map(methodArg => + methodArg._1 -> getResolvedVarsFromExp(methodArg._2, graphMap) + ).toMap + (heapVertex -> Set(heapVertex)) + val retVarMapping = (callTargets.map(l => getLocalVarDeclFromLocalVar(l)) zip method.formalArgs) + .map(vars => vars._2 -> vars._1).toMap + (heapVertex -> heapVertex) - val allMethodArgsSet = methodArgExpMapping.values.flatten.toSet + heap_vertex - - val retVarMapping = (retVars.map(l => getLocalVarDeclFromLocalVar(l)) zip methodRetVars) - .map(vars => vars._2 -> vars._1).toMap + (heap_vertex -> heap_vertex) - - /** Get influences defined in the influenced annotations */ - val annotationInfluences = posts.collect({case FlowAnnotation(returned, arguments) => (returned, arguments)}).map(t => { - val decl = retVarMapping(getLocalVarDeclFromFlowVar(t._1)) - decl -> (t._2.flatMap(v => methodArgExpMapping(getLocalVarDeclFromFlowVar(v))).toSet ++ graphMap(decl)) - }).toMap + if(!methodAnalysisMap.contains(method)) { + executeTaintedGraphMethodAnalysis(method) + } - /** influence all return variables, not mentioned by annotation, by every method argument */ - val otherInfluences = (retSet -- annotationInfluences.keySet).map(retVar => { - val decl = retVarMapping(retVar) - decl -> (allMethodArgsSet ++ graphMap(decl)) - }) - val methodInfluences = annotationInfluences ++ otherInfluences + val resolvedMethodMap = methodAnalysisMap(method) + .filter(v => retVarMapping.contains(v._1)) + .map(v => retVarMapping(v._1) -> v._2.flatMap(methodArgExpMapping)) + + (heapVertex -> (graphMap(heapVertex) ++ methodAnalysisMap(method)(heapVertex).flatMap(methodArgExpMapping))) - graphMap ++ methodInfluences - } + val methodAssumeAnalysis = methodAssumeAnalysisMap(method) - private def getLocalVarDeclFromFlowVar(f: FlowVar): LocalVarDecl = { - f match { - case v: Var => LocalVarDecl(v.decl.name, v.decl.typ)(v.decl.pos) - case _: Heap => heap_vertex - } + // We set the position to the method call instead of the assume statement, so potential error are more readable. + assumeAnalysis.addAll(methodAssumeAnalysis.map(v => pos -> (v._2.flatMap(v => methodArgExpMapping(v)) ++ pathInfluencingVars))) + graphMap ++ resolvedMethodMap } /** @@ -231,14 +269,58 @@ case class VarAnalysisGraphMap(prog: Program, case LocalVarAssign(l, _) => Set(getLocalVarDeclFromLocalVar(l)) case If(_, thn, els) => getModifiedVars(thn) ++ getModifiedVars(els) case While(_, _, body) => getModifiedVars(body) - case MethodCall(_, _, _) => Set() + case MethodCall(_, _, targets) => targets.map(getLocalVarDeclFromLocalVar).toSet case Inhale(_) => Set() case Assume(_) => Set() case Label(_, _) => Set() + case Quasihavoc(_, _) => Set(heapVertex) + case Quasihavocall(_, _, _) => Set(heapVertex) + case ExistentialElim(vars, _, _) => vars.toSet case _ => Set() } } + private def lookupVar(variable: LocalVarDecl, graphMap: GraphMap): Set[LocalVarDecl] = { + assert(graphMap.contains(variable), "Variable " + variable + " not found in graph analysis") + graphMap(variable) + } + + /** + * Gets all variables used in a given expressions and maps them to their influences specified in given graph map + */ + private def getResolvedVarsFromExp(exp: Exp, graphMap: GraphMap): Set[LocalVarDecl] = { + getLocalVarDeclsFromExpr(exp).flatMap(v => lookupVar(v, graphMap)) + } + + /** + * returns all the variables inside an expression + * + * @param exp expressions from which all variables should be returned + * @return set of Variable declarations + */ + def getLocalVarDeclsFromExpr(exp: Exp): Set[LocalVarDecl] = { + exp match { + case l@LocalVar(_, _) => Set(getLocalVarDeclFromLocalVar(l)) + case BinExp(exp1, exp2) => getLocalVarDeclsFromExpr(exp1) ++ getLocalVarDeclsFromExpr(exp2) + case UnExp(exp) => getLocalVarDeclsFromExpr(exp) + case f: FuncApp => + val heapDependent = Expressions.couldBeHeapDependent(f.func(prog), prog) + f.args.flatMap(e => getLocalVarDeclsFromExpr(e).filter(v => v.typ != Ref)).toSet ++ (if (heapDependent) Set(heapVertex) else Set()) + case DomainFuncApp(_, exps, _) => exps.flatMap(e => getLocalVarDeclsFromExpr(e)).toSet + case _: ForPerm | _: CurrentPerm => Set(heapVertex) + case FieldAccess(v, _) => getLocalVarDeclsFromExpr(v) + heapVertex + case AccessPredicate(access, _) => getLocalVarDeclsFromExpr(access) + heapVertex + case _ => Set() + } + } + + private def getLocalVarDeclFromFlowVar(f: FlowVar): LocalVarDecl = { + f match { + case v: Var => LocalVarDecl(v.decl.name, v.decl.typ)(v.decl.pos) + case _: Heap => heapVertex + } + } + private def getLocalVarDeclFromLocalVar(l: LocalVar): LocalVarDecl = { LocalVarDecl(l.name, l.typ)() } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala deleted file mode 100644 index 46d930d66..000000000 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisSet.scala +++ /dev/null @@ -1,177 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2011-2024 ETH Zurich. - -package viper.silver.plugin.standard.reasoning.analysis - -import viper.silver.ast.{Assume, BinExp, Declaration, Exp, FieldAssign, If, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, MethodCall, Seqn, Stmt, UnExp, While} -import viper.silver.plugin.standard.reasoning.UniversalIntro -import viper.silver.verifier.{AbstractError, ConsistencyError} - -import scala.collection.mutable - -trait VarAnalysisSet { - - def reportErrorWithMsg(error: AbstractError): Unit - - def executeTaintedSetAnalysis(tainted: Set[Declaration], vars_outside_blk: mutable.Set[Declaration], blk: Seqn, u: UniversalIntro, reportError: AbstractError => Unit): Unit = { - /** check whether any additional variables are tainted inside of the block */ - var all_tainted = Set[Declaration]() - all_tainted = get_tainted_vars_stmt(tainted, blk) - - /** remove the variables that were tainted to begin with */ - vars_outside_blk --= mutable.Set(u.transitiveScopedDecls: _*) - - /** check whether any variables were tainted that are declared outside of our block */ - if (vars_outside_blk.intersect(all_tainted).nonEmpty) { - val tainted_vars: Set[Declaration] = all_tainted.intersect(vars_outside_blk) - val problem_vars: String = tainted_vars.mkString(", ") - reportError(ConsistencyError(s"Universal introduction variable might have been assigned to variable $problem_vars, defined outside of the block", u.pos)) - } - } - - /** - * check which arguments are influenced by universal introduction variables and add them to the tainted set. - * @param tainted - * @param stmt - * @return - */ - def get_tainted_vars_stmt(tainted: Set[Declaration], stmt: Stmt): Set[Declaration] = { - var output: Set[Declaration] = tainted - stmt match { - case Seqn(ss, _) => - for (s <- ss) { - output = get_tainted_vars_stmt(output, s) - } - output - - case LocalVarAssign(lhs, rhs) => - if (is_expr_tainted(tainted, rhs)) { - tainted ++ Set(LocalVarDecl(lhs.name, lhs.typ)(stmt.pos)) - } else { - tainted -- Set(LocalVarDecl(lhs.name, lhs.typ)(stmt.pos)) - } - - case If(cond, thn, els) => - if (is_expr_tainted(tainted, cond)) { - val written_vars_thn = writtenTo(thn) - val written_vars_els = writtenTo(els) - Set[Declaration]() ++ written_vars_thn.getOrElse(mutable.Set()) ++ written_vars_els.getOrElse(mutable.Set()) - } else { - get_tainted_vars_stmt(tainted, thn) ++ get_tainted_vars_stmt(tainted, els) - } - - case w@While(cond, _, body) => - val new_tainted = get_tainted_vars_stmt(tainted, If(cond,body,Seqn(Seq(), Seq())(body.pos))(body.pos)) - if (new_tainted.equals(tainted)) { - tainted - } else { - get_tainted_vars_stmt(new_tainted, w) - } - - case m@MethodCall(_, _, _) => - reportErrorWithMsg(ConsistencyError("Might be not allowed method call inside universal introduction block", m.pos)) - tainted - - case i@Inhale(exp) => - if(exp.isPure) { - tainted - } else { - reportErrorWithMsg(ConsistencyError("There might be an impure inhale expression inside universal introduction block", i.pos)) - tainted - } - - case Assume(_) => tainted - - case Label(_, _) => tainted - - /** TODO: Do not allow Heap assignments */ - case f@FieldAssign(_, _) => - reportErrorWithMsg(ConsistencyError("FieldAssign for modular flow analysis with sets", f.pos)) - tainted - - case s@_ => - reportErrorWithMsg(ConsistencyError("undefined statement for modular flow analysis with set", s.pos)) - tainted - } - } - - - /** - * expressions that should be added to tainted (e.g. for instance for inhale statements - * @param tainted - * @param exp - * @return - */ - private def addExprToTainted(tainted: Set[Declaration], exp: Exp) : Set[Declaration] = { - exp match { - case l@LocalVar(_, _) => tainted ++ Set(LocalVarDecl(l.name, l.typ)(exp.pos)) - case BinExp(exp1, exp2) => addExprToTainted(tainted, exp1) ++ addExprToTainted(tainted, exp2) - case UnExp(exp) => addExprToTainted(tainted, exp) - case _ => tainted - } - } - - /** - * check whether expression contains a tainted variable - * @param tainted - * @param exp - * @return - */ - private def is_expr_tainted(tainted:Set[Declaration], exp:Exp) : Boolean = { - exp match { - case l@LocalVar(_, _) => isTainted(LocalVarDecl(l.name, l.typ)(l.pos), tainted) - case BinExp(exp1, exp2) => is_expr_tainted(tainted, exp1) || is_expr_tainted(tainted, exp2) - case UnExp(exp) => is_expr_tainted(tainted, exp) - case _ => false - } - } - - private def isTainted(name:Declaration, tainted:Set[Declaration]): Boolean = { - tainted.contains(name) - } - - /** - * return variables that are assigned new values - * @param stmt - * @return - */ - def writtenTo(stmt: Stmt): Option[Set[LocalVarDecl]] = { - var output: Option[Set[LocalVarDecl]] = None - stmt match { - case Seqn(ss, _) => - for (s <- ss) { - output match { - case None => output = writtenTo(s) - case Some(v) => output = Some(v ++ writtenTo(s).getOrElse(Set[LocalVarDecl]())) - } - } - output - - case LocalVarAssign(lhs, _) => - val lhs_var = LocalVarDecl(lhs.name, lhs.typ)(lhs.pos) - Some(Set(lhs_var)) - - case If(_, thn, els) => - val writtenThn = writtenTo(thn) - val writtenEls = writtenTo(els) - (writtenThn, writtenEls) match { - case (None,None) => None - case (Some(_), None) => writtenThn - case (None, Some(_)) => writtenEls - case (Some(t), Some(e)) => Some(t++e) - } - - case While(_, _, body) => writtenTo(body) - - /** TODO */ - case MethodCall(_, _, _) => None - case Inhale(_) => None - case Assume(_) => None - case Label(_, _) => None - case _ => None - } - } -} From 2e6d4e557473d31da778def52019c83728ba58db Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Tue, 16 Apr 2024 17:17:18 +0200 Subject: [PATCH 17/41] fix a bug in method handling & rename variables --- .../reasoning/analysis/VarAnalysisGraphMap.scala | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index 979d84871..1fad9ca10 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -51,7 +51,7 @@ case class VarAnalysisGraphMap(prog: Program, } else { "variables " + vars } - reportErrorWithMsg(ConsistencyError(s"Tainted $err might influence assume statement", v._1)) + reportErrorWithMsg(ConsistencyError(s"Tainted $err (${u.pos}) might influence assume statement", v._1)) } }) } @@ -72,11 +72,10 @@ case class VarAnalysisGraphMap(prog: Program, methodAssumeAnalysisMap.put(method, overApproximateAssumeInfluences(method)) } else { methodAnalysisStarted.addOne(method) - val varDecls = method.formalReturns.toSet ++ method.formalArgs.toSet + heapVertex - val initialGraph = varDecls.map(k => k -> Set(k)).toMap + (heapVertex -> Set(heapVertex)) + val initialGraph: GraphMap = (method.formalArgs.map(k => k -> Set(k)) ++ method.formalReturns.map(k => k -> Set[LocalVarDecl]())).toMap + (heapVertex -> Set(heapVertex)) val stmt = Seqn(method.body.get.ss, method.body.get.scopedSeqnDeclarations.filter({ - case l: LocalVarDecl => !varDecls.contains(l) + case l: LocalVarDecl => !initialGraph.contains(l) case _ => true }))(method.body.get.pos, method.body.get.info, method.body.get.errT) @@ -241,7 +240,7 @@ case class VarAnalysisGraphMap(prog: Program, val methodArgExpMapping = (method.formalArgs zip callArgs).map(methodArg => methodArg._1 -> getResolvedVarsFromExp(methodArg._2, graphMap) ).toMap + (heapVertex -> Set(heapVertex)) - val retVarMapping = (callTargets.map(l => getLocalVarDeclFromLocalVar(l)) zip method.formalArgs) + val retVarMapping = (callTargets.map(l => getLocalVarDeclFromLocalVar(l)) zip method.formalReturns) .map(vars => vars._2 -> vars._1).toMap + (heapVertex -> heapVertex) if(!methodAnalysisMap.contains(method)) { @@ -257,6 +256,11 @@ case class VarAnalysisGraphMap(prog: Program, // We set the position to the method call instead of the assume statement, so potential error are more readable. assumeAnalysis.addAll(methodAssumeAnalysis.map(v => pos -> (v._2.flatMap(v => methodArgExpMapping(v)) ++ pathInfluencingVars))) + + logger.warn("{}", methodAnalysisMap(method)) + logger.warn("{}", retVarMapping) + + logger.warn("{}", resolvedMethodMap) graphMap ++ resolvedMethodMap } From 9a5b9038be19ee250f1c7c6984163b5a0dfc2da7 Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Tue, 16 Apr 2024 17:17:29 +0200 Subject: [PATCH 18/41] ordered tests --- src/test/resources/reasoning/test3.vpr | 350 ------------------ .../universal_introduction/ui_assignments.vpr | 0 .../universal_introduction/ui_assume.vpr | 195 ++++++++++ .../universal_introduction/ui_func.vpr | 34 ++ .../universal_introduction/ui_heap.vpr | 66 ++++ .../universal_introduction/ui_if.vpr | 77 ++++ .../ui_methods.vpr} | 139 +++++++ .../universal_introduction/ui_while.vpr | 88 +++++ 8 files changed, 599 insertions(+), 350 deletions(-) delete mode 100644 src/test/resources/reasoning/test3.vpr create mode 100644 src/test/resources/reasoning/universal_introduction/ui_assignments.vpr create mode 100644 src/test/resources/reasoning/universal_introduction/ui_assume.vpr create mode 100644 src/test/resources/reasoning/universal_introduction/ui_func.vpr create mode 100644 src/test/resources/reasoning/universal_introduction/ui_heap.vpr create mode 100644 src/test/resources/reasoning/universal_introduction/ui_if.vpr rename src/test/resources/reasoning/{test2.vpr => universal_introduction/ui_methods.vpr} (53%) create mode 100644 src/test/resources/reasoning/universal_introduction/ui_while.vpr diff --git a/src/test/resources/reasoning/test3.vpr b/src/test/resources/reasoning/test3.vpr deleted file mode 100644 index e050b8e21..000000000 --- a/src/test/resources/reasoning/test3.vpr +++ /dev/null @@ -1,350 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/publicdomain/zero/1.0/ - -function P(x: Int) : Bool { - x == 0 -} - -function Q(x: Int) : Bool { - x == 0 -} - -method m0() -{ - var z:Int := 0 - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - var y:Int := x+1 - } - if (true) { var y:Int } // this should work -> two separate blocks - // var y:Int := 0 //here duplicate identifier -} -method mIndirect() -{ - var z:Int := 0 - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - var y:Int := x+1 - z := y // Consistency error - } - // problem if here var y:Int := 0 -> this will also be in tainted set - if (true) { var y:Int } -} - -method mIfCnd() -{ - var z: Int := 0 - var y: Int := 0 - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - if(x>5) { - z := 3 - } else { - y := 5 - } - } -} - -method mIfNOK() -{ - var w :Int := 0 - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - var l :Int := 0 - if (l >= -1) { - l := x - } else { - w := x - } - } -} - -method mIfOK1() -{ - var w :Int := 0 - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - var l :Int := 0 - var g: Int := 0 - if (l >= -1) { - g := x - } else { - w := 4 - } - } -} - -method mIfOK2() -{ - var w :Int := 0 - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - var l :Int := 0 - if (l >= -1) { - l := x - } else { - w := 4 // should be SAFE - } - } -} - -method mWhileCnd() -{ - var z: Int := 0 - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - while(x>5) { - z := z+3 - } - } -} - -method mWhileOK() -{ - var z: Int := 0 - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - var y: Int := 0 - while(y<5) { - z := z+3 - y := y+1 - } - } -} - -method mWhileOK2() -{ - var z: Int := 0 - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - var y: Int := 0 - while(y<100) { // will only be tainted after the 5th? iteration - var x4: Int := 0 - var x3: Int := 0 - var x2: Int := 0 - var x1: Int := 0 - z := x4 - x4 := x3 - x3 := x2 - x2 := x1 - x1 := x - y := y+1 - } - } -} - -method mWhileNOK() -{ - var z: Int := 0 - //:: ExpectedOutput(consistency.error) - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - var y: Int := 0 - var x2: Int := 0 - var x1: Int := 0 - - while(y<100) { // will only be tainted after the 5th? iteration - z := x2 - x2 := x1 - x1 := x - y := y+1 - } - } -} - -method mWhileNOK2() -{ - var z: Int := 0 - //:: ExpectedOutput(consistency.error) - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - var y: Int := x - var x2: Int := 0 - var x1: Int := 0 - - while(y<100) { // will only be tainted after the 5th? iteration - z := x2 - x2 := x1 - y := y+1 - } - } -} - -method mcall() -{ - var y: Int := 0 - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - mWhileOK() - } -} - -method example1(x:Int, y:Int) returns (res: Int) -influenced res by {x, y} -{ - res := x-y -} - -method mMethodCallNOK1() -{ - var z: Int - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - z := example1(x,x) - } -} - -//code without influenced by statement -method example2(x:Int, y:Int) returns (res: Int) -{ - res := 0 -} - -method mMethodCallNOK2() -{ - var z: Int - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - z := example2(x,x) - } -} - -//code with influenced by statement -method example3(x:Int, y:Int) returns (res: Int) -influenced res by {} -{ - res := 0 -} - -method mMethodCallOK1() -{ - var z: Int - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - var y:Int := 3 - z := example3(y,y) - } -} - -//method with several return values -method exampleMult(x:Int, y:Int) returns (diff:Int, id:Int) -influenced diff by {x, y} -influenced id by {x} -{ - diff := x-y - id := x -} - -method mMethodCallNOK3() -{ - var z: Int - var w: Int - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - var y: Int := 3 - z,w := exampleMult(y,x) - } -} - -method exampleIncomplete(b:Bool,c:Int) returns (z:Int, w:Int) -influenced w by {b} -{ - z := 3 - if (b) { - w := w + 1 - } -} - -method mMethodCallOK2() -{ - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - var y:Int - var v:Int - var bool:Bool := true - var count: Int := 16 - y,v := exampleIncomplete(bool,count) - } -} - - -method mAssume() -{ - var m: Int := 0 - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - assume(m==0) - } -} - -method mInhalingOK() -{ - var m: Int := 0 - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - var y: Int := 0 - inhale (y==0) - } -} - -field f: Int - -method mInhaleOK2(y: Ref) - -{ - - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - inhale (acc(y.f)) - } -} - -method mExhaleOK(y:Ref) -requires acc(y.f) -{ - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - exhale acc(y.f) - } -} - - -method mFieldAssignNOK1(y:Ref) -requires acc(y.f) -{ - //:: ExpectedOutput(consistency.error) - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - y.f := x - } -} - -method mFieldAssignNOK2(y:Ref) -{ - var w: Int - //:: ExpectedOutput(consistency.error) - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - var z: Int := x - w := z - } -} - - -method mFieldAssignOK1(y:Ref) -requires acc(y.f) -{ - var z: Int := 0 - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - z := y.f - } -} - - -function func1(x:Int) : Int -{ - x -} - -method mFunctionOK() -{ - var z: Int := 3 - var y: Int - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - y := func1(z) - } -} - -method mFunctionNOK() -{ - var z: Int := 3 - var y: Int - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - y := func1(x) - } -} diff --git a/src/test/resources/reasoning/universal_introduction/ui_assignments.vpr b/src/test/resources/reasoning/universal_introduction/ui_assignments.vpr new file mode 100644 index 000000000..e69de29bb diff --git a/src/test/resources/reasoning/universal_introduction/ui_assume.vpr b/src/test/resources/reasoning/universal_introduction/ui_assume.vpr new file mode 100644 index 000000000..487d637b5 --- /dev/null +++ b/src/test/resources/reasoning/universal_introduction/ui_assume.vpr @@ -0,0 +1,195 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +function P(x: Int) : Bool { + x == 0 +} + +function Q(x: Int) : Bool { + x == 0 +} + +method assumeF() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + //:: ExpectedOutput(consistency.error) + assume(x==0) + + //:: ExpectedOutput(consistency.error) + assume(P(x) && Q(10)) + if(x==0) { + //:: ExpectedOutput(consistency.error) + assume(true) + } + while(x > 0) { + //:: ExpectedOutput(consistency.error) + assume(true) + } + } +} + +method assumeAssignF() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + var y: Int := x + //:: ExpectedOutput(consistency.error) + assume(y==0) + + if(y==0) { + //:: ExpectedOutput(consistency.error) + assume(true) + } + while(y > 0) { + //:: ExpectedOutput(consistency.error) + assume(true) + } + } +} + +method assumeMultipleF() +{ + prove forall x: Int, y: Int {P(x), P(y)} assuming P(x) && P(y) implies Q(x) && Q(y) { + //:: ExpectedOutput(consistency.error) + assume(y==x) + + if(y==x) { + //:: ExpectedOutput(consistency.error) + assume(true) + } + while(y > x) { + //:: ExpectedOutput(consistency.error) + assume(true) + } + } +} + + +method assumeMultipleNestedF() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + prove forall y: Int {P(y)} assuming P(y) implies Q(y) { + //:: ExpectedOutput(consistency.error) + assume(y==x) + + if(y==x) { + //:: ExpectedOutput(consistency.error) + assume(true) + } + while(y > x) { + //:: ExpectedOutput(consistency.error) + assume(true) + } + } + } +} + +method assumeS() +{ + var y: Int := 0 + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + assume(y==0) + if(y==0) { + assume(true) + } + while(y > 0) { + assume(true) + } + } +} + +method assumeMethodCallF() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + //:: ExpectedOutput(consistency.error) + assumeSomething(x, x) + if(x > 0) { + //:: ExpectedOutput(consistency.error) + assumeSomething(10, 0) + } + while(x > 0) { + //:: ExpectedOutput(consistency.error) + assumeSomething(10, 0) + } + } +} + +method assumeMethodCallTaintedVarF() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + var y: Int := x + 1 + //:: ExpectedOutput(consistency.error) + assumeSomething(y, y) + if(y > 0) { + //:: ExpectedOutput(consistency.error) + assumeSomething(10, 0) + } + while(y > 0) { + //:: ExpectedOutput(consistency.error) + assumeSomething(10, 0) + } + } +} + +method assumeRecMethodCallF() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + //:: ExpectedOutput(consistency.error) + assumeRec1(x) + + if(x > 0) { + //:: ExpectedOutput(consistency.error) + assumeRec1(10) + } + while(x > 0) { + //:: ExpectedOutput(consistency.error) + assumeRec1(10) + } + } +} + +method assumeDeepRecMethodCallF() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + //:: ExpectedOutput(consistency.error) + assumeRec2(x) + if(x > 0) { + //:: ExpectedOutput(consistency.error) + assumeRec2(10) + } + while(x > 0) { + //:: ExpectedOutput(consistency.error) + assumeRec2(10) + } + } +} + +method assumeDeepRecMethodCall1S() +{ + var y: Int := 0 + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + assumeRec2(y) + } +} + +method assumeRec1(x: Int) { + assumeRec1(x - 1) + assumeSomething(x, x) +} + +method assumeRec2(x: Int) { + assumeRec3(x - 1) +} + +method assumeRec3(x: Int) { + assumeRec4(x - 1) +} + +method assumeRec4(x: Int) { + assumeRec2(x - 1) + assumeSomething(x, x) +} + +method assumeSomething(x: Int, y: Int) +{ + assume(x >= y) +} \ No newline at end of file diff --git a/src/test/resources/reasoning/universal_introduction/ui_func.vpr b/src/test/resources/reasoning/universal_introduction/ui_func.vpr new file mode 100644 index 000000000..33cd76512 --- /dev/null +++ b/src/test/resources/reasoning/universal_introduction/ui_func.vpr @@ -0,0 +1,34 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +function P(x: Int) : Bool { + x == 0 +} + +function Q(x: Int) : Bool { + x == 0 +} + +function func1(x:Int) : Int +{ + x +} + +method mFunctionOK() +{ + var z: Int := 3 + var y: Int + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && y == z { + y := func1(z) + } +} + +method mFunctionNOK() +{ + var z: Int := 3 + var y: Int + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && y >= 0 || y < 0 { + y := func1(x) + } +} diff --git a/src/test/resources/reasoning/universal_introduction/ui_heap.vpr b/src/test/resources/reasoning/universal_introduction/ui_heap.vpr new file mode 100644 index 000000000..48b61f4ee --- /dev/null +++ b/src/test/resources/reasoning/universal_introduction/ui_heap.vpr @@ -0,0 +1,66 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +function P(x: Int) : Bool { + x == 0 +} + +function Q(x: Int) : Bool { + x == 0 +} + +method mInhalingOK() +{ + var m: Int := 0 + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + var y: Int := 0 + inhale (y==0) + } +} + +field f: Int + +method mInhaleOK2(y: Ref) +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + inhale (acc(y.f)) + } +} + +method mExhaleOK(y:Ref) +requires acc(y.f) +{ + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + exhale acc(y.f) + } +} + + +method mFieldAssignNOK1(y:Ref) +requires acc(y.f) +{ + //:: ExpectedOutput(consistency.error) + prove forall x: Int {P(x)} assuming P(x) implies Q(x) && y.f == x { + y.f := x + } +} + +method mFieldAssignNOK2(y:Ref) +{ + var w: Int + //:: ExpectedOutput(consistency.error) + prove forall x: Int {P(x)} assuming P(x) implies Q(x) && w == x { + var z: Int := x + w := z + } +} + + +method mFieldAssignOK1(y:Ref) +requires acc(y.f) +{ + var z: Int := 0 + prove forall x: Int {P(x)} assuming P(x) implies Q(x) && z >= 0 || z < 0 { + z := y.f + } +} \ No newline at end of file diff --git a/src/test/resources/reasoning/universal_introduction/ui_if.vpr b/src/test/resources/reasoning/universal_introduction/ui_if.vpr new file mode 100644 index 000000000..283672851 --- /dev/null +++ b/src/test/resources/reasoning/universal_introduction/ui_if.vpr @@ -0,0 +1,77 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +function P(x: Int) : Bool { + x == 0 +} + +function Q(x: Int) : Bool { + x == 0 +} + +method mIndirect() +{ + var z:Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && z == x { + var y:Int := x+1 + z := y // Consistency error + } + // problem if here var y:Int := 0 -> this will also be in tainted set + if (true) { var y:Int } +} + +method mIfCnd() +{ + var z: Int := 0 + var y: Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && y == 5 { + if(x>5) { + z := 3 + } else { + y := 5 + } + } +} + +method mIfNOK() +{ + var w :Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && w == x { + var l :Int := 0 + if (l >= -1) { + l := x + } else { + w := x + } + } +} + +method mIfOK1() +{ + var w :Int := 0 + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && w == 4 { + var l :Int := 0 + var g: Int := 0 + if (l >= -1) { + g := x + } else { + w := 4 + } + } +} + +method mIfOK2() +{ + var w :Int := 0 + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && w == 4 { + var l :Int := 0 + if (l >= -1) { + l := x + } else { + w := 4 // should be SAFE + } + } +} \ No newline at end of file diff --git a/src/test/resources/reasoning/test2.vpr b/src/test/resources/reasoning/universal_introduction/ui_methods.vpr similarity index 53% rename from src/test/resources/reasoning/test2.vpr rename to src/test/resources/reasoning/universal_introduction/ui_methods.vpr index 19be7dfb7..3d425e493 100644 --- a/src/test/resources/reasoning/test2.vpr +++ b/src/test/resources/reasoning/universal_introduction/ui_methods.vpr @@ -1,8 +1,147 @@ // Any copyright is dedicated to the Public Domain. // http://creativecommons.org/publicdomain/zero/1.0/ +function P(x: Int) : Bool { + x == 0 +} + +function Q(x: Int) : Bool { + x == 0 +} + +method example1(x:Int, y:Int) returns (res: Int) +influenced res by {x, y} +{ + res := x-y +} + +method example2(x:Int, y:Int) returns (res: Int) +{ + res := 0 +} + +method mMethodCallNOK1() +{ + var z: Int + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && z == 0 { + z := example1(x,x) + } +} + +method mMethodCallNOK2() +{ + var z: Int + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && z == 0 { + z := example2(x,x) + } +} + +//code with influenced by statement +method example3(x:Int, y:Int) returns (res: Int) +influenced res by {} +{ + res := 0 +} + +method mMethodCallOK1() +{ + var z: Int + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && z == 0 { + var y:Int := 3 + z := example3(y,y) + } +} + +//method with several return values +method exampleMult(x:Int, y:Int) returns (diff:Int, id:Int) +influenced diff by {x, y} +influenced id by {x} +{ + diff := x-y + id := x +} + +method mMethodCallNOK3() +{ + var z: Int + var w: Int + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && z == x - 3 && w == x { + var y: Int := 3 + z,w := exampleMult(y,x) + } +} + +method exampleIncomplete(b:Bool,c:Int) returns (z:Int, w:Int) +influenced w by {b} +{ + z := 3 + if (b) { + w := w + 1 + } +} + +method mMethodCallNOK4() +{ + var y:Int + var v:Int + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && y == 3 && v == x + 1 { + var bool:Bool := true + y,v := exampleIncomplete(bool,x) + } +} + +method exampleCompleteInfluence(b:Bool,c:Int) returns (z:Int, w:Int) +influenced w by {b} +influenced z by {} +{ + z := 3 + if (b) { + w := 13 + } +} + +method mMethodCallOK4() +{ + var y:Int + var v:Int + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && y == 3 && v == x + 1 { + var bool:Bool := true + y,v := exampleCompleteInfluence(bool,x) + } +} + + + field f: Int +method test() +{ + var r: Ref + var p: Perm + var i: Int + var j: Int + prove forall x:Int {P(x)} assuming P(x) implies Q(x) { + r := test1(r) + r := test2(r) + p := test3(p) + p := test4(p) + j, i := test5(i, j) + j, i := test5_equivalent(i, j) + j, i := test5_caller(i, j) + j, i := test6(i, j) + j, i := test7(i, j) + j, i := caller_test6(i, j, r) + j, i := caller_test6(i, j, r) + caller_caller_test6(i, j, r) + test8(r, j) + test9(r, i) + } +} + + // Should be accepted method test1(x: Ref) returns (z: Ref) influenced z by {x} diff --git a/src/test/resources/reasoning/universal_introduction/ui_while.vpr b/src/test/resources/reasoning/universal_introduction/ui_while.vpr new file mode 100644 index 000000000..45365abd5 --- /dev/null +++ b/src/test/resources/reasoning/universal_introduction/ui_while.vpr @@ -0,0 +1,88 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +function P(x: Int) : Bool { + x == 0 +} + +function Q(x: Int) : Bool { + x == 0 +} + +method mWhileCnd() +{ + var z: Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && z >= 0 { + while(x>5) { + z := z+3 + } + } +} + +method mWhileOK() +{ + var z: Int := 0 + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && z >= 0 { + var y: Int := 0 + while(y<5) { + z := z+3 + y := y+1 + } + } +} + +method mWhileOK2() +{ + var z: Int := 0 + prove forall x: Int {P(x)} assuming P(x) implies Q(x) && z == 0 { + var y: Int := 0 + while(y<100) { // will never be tainted as the variables are local + var x4: Int := 0 + var x3: Int := 0 + var x2: Int := 0 + var x1: Int := 0 + z := x4 + x4 := x3 + x3 := x2 + x2 := x1 + x1 := x + y := y+1 + } + } +} + +method mWhileNOK() +{ + var z: Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x: Int {P(x)} assuming P(x) implies Q(x) && z == x { + var y: Int := 0 + var x2: Int := 0 + var x1: Int := 0 + + while(y<100) { // will be tainted after 3 iterations + z := x2 + x2 := x1 + x1 := x + y := y+1 + } + } +} + +method mWhileNOK2() +{ + var z: Int := 0 + //:: ExpectedOutput(consistency.error) + prove forall x: Int {P(x)} assuming P(x) implies Q(x) && z == 0 { + var y: Int := x + var x2: Int := 0 + var x1: Int := 0 + + while(y<100) { // will always be tainted + z := x2 + x2 := x1 + y := y+1 + } + } +} \ No newline at end of file From e558efa023b8b957eacbf3a63e4b691c9cc651c4 Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Thu, 18 Apr 2024 08:56:30 +0200 Subject: [PATCH 19/41] add more tests & cleanup --- .../reasoning/BeforeVerifyHelper.scala | 32 ++ .../standard/reasoning/ReasoningPlugin.scala | 5 +- .../analysis/VarAnalysisGraphMap.scala | 112 ++-- src/test/resources/reasoning/test.vpr | 523 ------------------ .../universal_introduction/ui_assignments.vpr | 0 .../universal_introduction/ui_heap.vpr | 26 + .../universal_introduction/ui_methods.vpr | 13 +- 7 files changed, 126 insertions(+), 585 deletions(-) delete mode 100644 src/test/resources/reasoning/test.vpr delete mode 100644 src/test/resources/reasoning/universal_introduction/ui_assignments.vpr diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala index 108be85e2..27e478ce0 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala @@ -11,6 +11,7 @@ import viper.silver.ast.utility.Expressions import viper.silver.ast.{Apply, Exhale, Exp, FieldAssign, Fold, Inhale, LocalVarDecl, Method, MethodCall, Package, Program, Seqn, Stmt, Unfold} import viper.silver.plugin.standard.termination.{DecreasesSpecification, DecreasesStar, DecreasesTuple, DecreasesWildcard} import viper.silver.verifier.{AbstractError, ConsistencyError} +import viper.silver.plugin.standard.reasoning.analysis.AnalysisUtils import scala.collection.mutable @@ -150,4 +151,35 @@ trait BeforeVerifyHelper { pure } } + + /** checks that influences by annotations are used correctly. */ + def checkInfluencedBy(input: Program, reportError: AbstractError => Unit): Unit = { + input.methods.foreach(method => { + val argVars = method.formalArgs ++ AnalysisUtils.heapVertex + val retVars = method.formalReturns ++ AnalysisUtils.heapVertex + + val seenVars: mutable.Set[LocalVarDecl] = mutable.Set() + /** iterate through method postconditions to find flow annotations */ + method.posts.foreach { + case v@FlowAnnotation(target, args) => + val targetVarDecl = AnalysisUtils.getLocalVarDeclFromFlowVar(target) + + if (!retVars.contains(targetVarDecl)) { + reportError(ConsistencyError(s"Only return variables can be influenced. ${targetVarDecl.name} is not be a return variable.", v.pos)) + } + if (seenVars.contains(targetVarDecl)) { + reportError(ConsistencyError(s"Only one influenced by expression per return variable can exist. ${targetVarDecl.name} is used several times.", v.pos)) + } + seenVars.add(targetVarDecl) + + args.foreach(arg => { + val argVarDecl = AnalysisUtils.getLocalVarDeclFromFlowVar(arg) + if (!argVars.contains(argVarDecl)) { + reportError(ConsistencyError(s"Only method arguments can influence a return variable. ${argVarDecl.name} is not be a method argument.", v.pos)) + } + }) + case _ => () + } + }) + } } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index bf9838e8b..31cf512e3 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -12,7 +12,7 @@ import viper.silver.ast.utility.rewriter.{StrategyBuilder, Traverse} import viper.silver.ast.utility.ViperStrategy import viper.silver.parser.FastParserCompanion.whitespace import viper.silver.parser._ -import viper.silver.plugin.standard.reasoning.analysis.VarAnalysisGraphMap +import viper.silver.plugin.standard.reasoning.analysis.{AnalysisUtils, VarAnalysisGraphMap} import viper.silver.plugin.{ParserPluginTemplate, SilverPlugin} import viper.silver.verifier._ @@ -256,8 +256,9 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, * get all variables that are assigned to inside the block and take intersection with universal introduction * variables. If they are contained throw error since quantified variables should be immutable */ - val writtenVars: Set[LocalVarDecl] = analysis.getModifiedVars(blk) + val writtenVars: Set[LocalVarDecl] = AnalysisUtils.getModifiedVars(blk) checkReassigned(writtenVars, v, reportError, u) + checkInfluencedBy(input, reportError) /** Contains all variables that must not be tainted */ val volatileVars: Set[LocalVarDecl] = analysis.getLocalVarDeclsFromExpr(exp1) ++ analysis.getLocalVarDeclsFromExpr(exp2) -- v diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index 1fad9ca10..9c3b525ad 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -8,11 +8,48 @@ package viper.silver.plugin.standard.reasoning.analysis import viper.silver.ast.utility.Expressions import viper.silver.ast.{AccessPredicate, Apply, Assert, Assume, BinExp, CurrentPerm, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, Fold, ForPerm, FuncApp, Goto, If, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, Method, MethodCall, Package, Position, Program, Quasihavoc, Quasihavocall, Ref, Scope, Seqn, Stmt, UnExp, Unfold, While} +import viper.silver.plugin.standard.reasoning.analysis.AnalysisUtils.{getLocalVarDeclFromLocalVar, heapVertex} import viper.silver.plugin.standard.reasoning.{ExistentialElim, FlowAnnotation, FlowVar, Heap, OldCall, UniversalIntro, Var} import viper.silver.verifier.{AbstractError, ConsistencyError} import scala.collection.mutable +object AnalysisUtils { + val heapVertex: LocalVarDecl = LocalVarDecl(".heap", Ref)() + + def getLocalVarDeclFromFlowVar(f: FlowVar): LocalVarDecl = { + f match { + case v: Var => LocalVarDecl(v.decl.name, v.decl.typ)(v.decl.pos) + case _: Heap => heapVertex + } + } + + /** + * get the variables that were modified by the statement stmt + */ + def getModifiedVars(stmt: Stmt): Set[LocalVarDecl] = { + stmt match { + case Seqn(ss, _) => ss.flatMap(s => getModifiedVars(s)).toSet + case LocalVarAssign(l, _) => Set(getLocalVarDeclFromLocalVar(l)) + case If(_, thn, els) => getModifiedVars(thn) ++ getModifiedVars(els) + case While(_, _, body) => getModifiedVars(body) + case MethodCall(_, _, targets) => targets.map(getLocalVarDeclFromLocalVar).toSet + case Inhale(_) => Set() + case Assume(_) => Set() + case Label(_, _) => Set() + case Quasihavoc(_, _) => Set(heapVertex) + case Quasihavocall(_, _, _) => Set(heapVertex) + case ExistentialElim(vars, _, _) => vars.toSet + case _ => Set() + } + } + + def getLocalVarDeclFromLocalVar(l: LocalVar): LocalVarDecl = { + LocalVarDecl(l.name, l.typ)() + } +} + + case class VarAnalysisGraphMap(prog: Program, logger: ch.qos.logback.classic.Logger, reportErrorWithMsg: AbstractError => Unit) { @@ -22,7 +59,6 @@ case class VarAnalysisGraphMap(prog: Program, // Maps all influences for a statement at a given position private type AssumeAnalysis = mutable.Map[Position, Set[LocalVarDecl]] - private val heapVertex: LocalVarDecl = LocalVarDecl(".heap", Ref)() // Storage for method analysis private val methodAnalysisMap: mutable.Map[Method, GraphMap] = mutable.Map() @@ -34,7 +70,7 @@ case class VarAnalysisGraphMap(prog: Program, * When executed on the universal introduction statement the tainted variables are simply the quantified variables */ def executeTaintedGraphAnalysis(allVars: Set[LocalVarDecl], tainted: Set[LocalVarDecl], blk: Seqn, volatileVars: Set[LocalVarDecl], u: UniversalIntro): Unit = { // Build initial graph where every variable and the heap influences itself - val initialGraph = allVars.map(k => k -> Set(k)).toMap + (heapVertex -> Set(heapVertex)) + val initialGraph = allVars.map(k => k -> Set(k)).toMap + (AnalysisUtils.heapVertex -> Set(AnalysisUtils.heapVertex)) val assumeAnalysis: AssumeAnalysis = mutable.Map() val graph = computeInfluenceMap(blk, initialGraph, Set())(assumeAnalysis) val problems = volatileVars.filter(v => graph(v).intersect(tainted).nonEmpty) @@ -72,7 +108,7 @@ case class VarAnalysisGraphMap(prog: Program, methodAssumeAnalysisMap.put(method, overApproximateAssumeInfluences(method)) } else { methodAnalysisStarted.addOne(method) - val initialGraph: GraphMap = (method.formalArgs.map(k => k -> Set(k)) ++ method.formalReturns.map(k => k -> Set[LocalVarDecl]())).toMap + (heapVertex -> Set(heapVertex)) + val initialGraph: GraphMap = (method.formalArgs.map(k => k -> Set(k)) ++ method.formalReturns.map(k => k -> Set[LocalVarDecl]())).toMap + (AnalysisUtils.heapVertex -> Set(AnalysisUtils.heapVertex)) val stmt = Seqn(method.body.get.ss, method.body.get.scopedSeqnDeclarations.filter({ case l: LocalVarDecl => !initialGraph.contains(l) @@ -85,8 +121,8 @@ case class VarAnalysisGraphMap(prog: Program, if(!methodAnalysisMap.contains(method)) { // Check calculated value against the provided specification if there are any method.posts.collect({ case f: FlowAnnotation => f }).foreach(f => { - val returnVar = getLocalVarDeclFromFlowVar(f.v) - val specifiedInfluences = f.varList.map(getLocalVarDeclFromFlowVar).toSet + val returnVar = AnalysisUtils.getLocalVarDeclFromFlowVar(f.v) + val specifiedInfluences = f.varList.map(AnalysisUtils.getLocalVarDeclFromFlowVar).toSet val calculatedInfluences = lookupVar(returnVar, map) if (calculatedInfluences != specifiedInfluences) { reportErrorWithMsg(ConsistencyError(s"Specified influence on return variable $returnVar differs from calculated value. Specified: $specifiedInfluences Calculated: $calculatedInfluences", f.pos)) @@ -105,11 +141,11 @@ case class VarAnalysisGraphMap(prog: Program, * Otherwise we over approximate by assuming every method arg (including the heap) influences the return variable. */ private def getDefaultMethodInfluences(method: Method): GraphMap = { - val retSet = method.formalReturns.toSet + heapVertex - val allMethodArgsSet = method.formalArgs.toSet + heapVertex + val retSet = method.formalReturns.toSet + AnalysisUtils.heapVertex + val allMethodArgsSet = method.formalArgs.toSet + AnalysisUtils.heapVertex val annotationInfluences = method.posts.collect({ case FlowAnnotation(returned, arguments) => (returned, arguments) }).map(t => { - getLocalVarDeclFromFlowVar(t._1) -> t._2.map(getLocalVarDeclFromFlowVar).toSet + AnalysisUtils.getLocalVarDeclFromFlowVar(t._1) -> t._2.map(AnalysisUtils.getLocalVarDeclFromFlowVar).toSet }).toMap /** influence all return variables, not mentioned by annotation, by every method argument */ val otherInfluences = (retSet -- annotationInfluences.keySet).map(retVar => { @@ -135,6 +171,7 @@ case class VarAnalysisGraphMap(prog: Program, val assumeAnalysis: AssumeAnalysis = mutable.Map() assumeAnalysis.addAll(allInhales.map(i => i.pos -> method.formalArgs.toSet)) } + private def getAllCalledMethods(method: Method): Set[Method] = method.deepCollectInBody({ case a: MethodCall => a }).map(m => prog.findMethod(m.methodName)).toSet private def computeInfluenceMap(stmt: Stmt, graphMap: GraphMap, pathInfluencingVariables: Set[LocalVarDecl])(implicit assumeAnalysis: AssumeAnalysis): GraphMap = { @@ -153,17 +190,17 @@ case class VarAnalysisGraphMap(prog: Program, // The quantified variables of the Quasihavocall statement are ignored because they are untainted by definition case Quasihavocall(_, lhs, _) => val vars = lhs.map(e => getResolvedVarsFromExp(e, graphMap)).getOrElse(Set()) - graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars)) + graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars)) case u: UniversalIntro => computeInfluenceMap(u.block, scopedGraph, pathInfluencingVariables) } graph.removedAll(declarations) case LocalVarAssign(lhs, rhs) => val influences = getResolvedVarsFromExp(rhs, graphMap).flatMap(v => lookupVar(v, graphMap)) - graphMap + (getLocalVarDeclFromLocalVar(lhs) -> influences) + graphMap + (AnalysisUtils.getLocalVarDeclFromLocalVar(lhs) -> influences) case If(cond, thn, els) => val conditionVars = getResolvedVarsFromExp(cond, graphMap) // For the condition influences, we only care for variables that are declared outside of the if block - val writesInIfBlocks = (getModifiedVars(thn) ++ getModifiedVars(els)).filter(v => graphMap.contains(v)) + val writesInIfBlocks = (AnalysisUtils.getModifiedVars(thn) ++ AnalysisUtils.getModifiedVars(els)).filter(v => graphMap.contains(v)) val conditionInfluences = writesInIfBlocks.map(v => v -> (lookupVar(v, graphMap) ++ conditionVars.flatMap(c => lookupVar(c, graphMap)))).toMap val thenGraph = computeInfluenceMap(thn, graphMap, pathInfluencingVariables ++ conditionVars) @@ -189,29 +226,29 @@ case class VarAnalysisGraphMap(prog: Program, computeMethodInfluenceMap(graphMap, met, m.args, m.targets, pathInfluencingVariables, m.pos) case FieldAssign(_, rhs) => val vars = getResolvedVarsFromExp(rhs, graphMap) - graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars.filter(v => !v.equals(heapVertex)).flatMap(v => graphMap(v)))) + graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars.filter(v => !v.equals(AnalysisUtils.heapVertex)).flatMap(v => graphMap(v)))) case Exhale(exp) => if (exp.isPure) { graphMap } else { val vars = getResolvedVarsFromExp(exp, graphMap) - graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars)) + graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars)) } case Fold(acc) => val vars = getResolvedVarsFromExp(acc, graphMap) - graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars)) + graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars)) case Unfold(acc) => val vars = getResolvedVarsFromExp(acc, graphMap) - graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars)) + graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars)) case Apply(exp) => val vars = getResolvedVarsFromExp(exp, graphMap) - graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars)) + graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars)) case Package(wand, _) => val vars = getResolvedVarsFromExp(wand, graphMap) - graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars)) + graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars)) case Quasihavoc(lhs, _) => val vars = lhs.map(e => getResolvedVarsFromExp(e, graphMap)).getOrElse(Set()) - graphMap + (heapVertex -> (lookupVar(heapVertex, graphMap) ++ vars)) + graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars)) case Assert(_) => graphMap case Label(_, _) => graphMap // Assume analysis @@ -239,9 +276,9 @@ case class VarAnalysisGraphMap(prog: Program, /** set of all target variables that have not been included in the influenced by expression up until now */ val methodArgExpMapping = (method.formalArgs zip callArgs).map(methodArg => methodArg._1 -> getResolvedVarsFromExp(methodArg._2, graphMap) - ).toMap + (heapVertex -> Set(heapVertex)) - val retVarMapping = (callTargets.map(l => getLocalVarDeclFromLocalVar(l)) zip method.formalReturns) - .map(vars => vars._2 -> vars._1).toMap + (heapVertex -> heapVertex) + ).toMap + (AnalysisUtils.heapVertex -> Set(AnalysisUtils.heapVertex)) + val retVarMapping = (callTargets.map(l => AnalysisUtils.getLocalVarDeclFromLocalVar(l)) zip method.formalReturns) + .map(vars => vars._2 -> vars._1).toMap + (AnalysisUtils.heapVertex -> AnalysisUtils.heapVertex) if(!methodAnalysisMap.contains(method)) { executeTaintedGraphMethodAnalysis(method) @@ -250,7 +287,7 @@ case class VarAnalysisGraphMap(prog: Program, val resolvedMethodMap = methodAnalysisMap(method) .filter(v => retVarMapping.contains(v._1)) .map(v => retVarMapping(v._1) -> v._2.flatMap(methodArgExpMapping)) + - (heapVertex -> (graphMap(heapVertex) ++ methodAnalysisMap(method)(heapVertex).flatMap(methodArgExpMapping))) + (AnalysisUtils.heapVertex -> (graphMap(AnalysisUtils.heapVertex) ++ methodAnalysisMap(method)(AnalysisUtils.heapVertex).flatMap(methodArgExpMapping))) val methodAssumeAnalysis = methodAssumeAnalysisMap(method) @@ -264,26 +301,6 @@ case class VarAnalysisGraphMap(prog: Program, graphMap ++ resolvedMethodMap } - /** - * get the variables that were modified by the statement stmt - */ - def getModifiedVars(stmt: Stmt): Set[LocalVarDecl] = { - stmt match { - case Seqn(ss, _) => ss.flatMap(s => getModifiedVars(s)).toSet - case LocalVarAssign(l, _) => Set(getLocalVarDeclFromLocalVar(l)) - case If(_, thn, els) => getModifiedVars(thn) ++ getModifiedVars(els) - case While(_, _, body) => getModifiedVars(body) - case MethodCall(_, _, targets) => targets.map(getLocalVarDeclFromLocalVar).toSet - case Inhale(_) => Set() - case Assume(_) => Set() - case Label(_, _) => Set() - case Quasihavoc(_, _) => Set(heapVertex) - case Quasihavocall(_, _, _) => Set(heapVertex) - case ExistentialElim(vars, _, _) => vars.toSet - case _ => Set() - } - } - private def lookupVar(variable: LocalVarDecl, graphMap: GraphMap): Set[LocalVarDecl] = { assert(graphMap.contains(variable), "Variable " + variable + " not found in graph analysis") graphMap(variable) @@ -317,15 +334,4 @@ case class VarAnalysisGraphMap(prog: Program, case _ => Set() } } - - private def getLocalVarDeclFromFlowVar(f: FlowVar): LocalVarDecl = { - f match { - case v: Var => LocalVarDecl(v.decl.name, v.decl.typ)(v.decl.pos) - case _: Heap => heapVertex - } - } - - private def getLocalVarDeclFromLocalVar(l: LocalVar): LocalVarDecl = { - LocalVarDecl(l.name, l.typ)() - } } diff --git a/src/test/resources/reasoning/test.vpr b/src/test/resources/reasoning/test.vpr deleted file mode 100644 index 324567bf9..000000000 --- a/src/test/resources/reasoning/test.vpr +++ /dev/null @@ -1,523 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/publicdomain/zero/1.0/ - -function P(x: Int) : Bool { - x == 0 -} - -function Q(x: Int) : Bool { - x == 0 -} -field f: Int - -method simple() -{ - var z: Int := 0 - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - z := x - } -} - - -method mIfOK1() -{ - var w :Int := 0 - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - var l :Int := 0 - var g: Int := 0 - if (l >= -1) { - g := x - } else { - w := 4 - } - } -} - - - -method mWhileOK() -{ - var z: Int := 0 - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - var y: Int := 0 - while(y<100) { // will only be tainted after the 5th? iteration - var x4: Int := 0 - var x3: Int := 0 - var x2: Int := 0 - var x1: Int := 0 - z := x4 - x4 := x3 - x3 := x2 - x2 := x1 - x1 := x - y := y+1 - } - } -} - - -method mWhileNOK() -{ - var z: Int := 0 - //:: ExpectedOutput(consistency.error) - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - var y: Int := 0 - var x2: Int := 0 - var x1: Int := 0 - - while(y<100) { // will only be tainted after the 5th? iteration - z := x2 - x2 := x1 - x1 := x - y := y+1 - } - } -} - - -//graph at the end such that z is influenced by .init_z and .init_x -//correct because if loop not executed then z is influenced by .init_z? -method mWhileNOK2() -{ - var z: Int := 0 - //:: ExpectedOutput(consistency.error) - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - var y: Int := x - var x2: Int := 0 - var x1: Int := 0 - - while(y<100) { - z := x2 - x2 := x1 - y := y+1 - } - } -} - - - - -method mFieldAssignNOK1(y:Ref) -requires acc(y.f) -{ - //:: ExpectedOutput(consistency.error) - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - y.f := x - } -} - -method mFieldAssignNOK2(y:Ref) -{ - var w: Int - //:: ExpectedOutput(consistency.error) - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - var z: Int := x - w := z - } -} - - -method mFieldAssignOK1(y:Ref) -requires acc(y.f) -{ - var z: Int := 0 - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - z := y.f - } -} - - -method mInhaleOK2(y: Ref) - -{ - - prove forall x: Int {P(x)} assuming P(x) implies Q(x) { - inhale (acc(y.f)) - } -} - - -method mExhaleOK(y:Ref) -requires acc(y.f) -{ - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - exhale acc(y.f) - } -} - - -function func1(x:Int) : Int -{ - x -} - -method mFunctionOK() -{ - var z: Int := 3 - var y: Int - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - y := func1(z) - } -} - -method mFunctionNOK() -{ - var z: Int := 3 - var y: Int - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - y := func1(x) - } -} - - - -method example1(x:Int, y:Int) returns (res: Int) -influenced res by {x, y} -{ - res := x-y -} - -method mMethodCallNOK1() -{ - var z: Int - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - z := example1(x,x) - } -} - -//code without influenced by statement -method example2(x:Int, y:Int) returns (res: Int) -{ - res := 0 -} - -method mMethodCallNOK2() -{ - var z: Int - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - z := example2(x,x) - } -} - -//code with influenced by statement -method example3(x:Int, y:Int) returns (res: Int) -influenced res by {} -{ - res := 0 -} - -method mMethodCallOK1() -{ - var z: Int - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - var y:Int := 3 - z := example3(y,y) - } -} - -//method with several return values -method exampleMult(a0:Int, a1:Int) returns (r0:Int, r1:Int) -influenced r0 by {a0, a1} -influenced r1 by {a0} -{ - r0 := a0-a1 - r1 := a0 -} - -method mMethodCallNOK3() -{ - var z: Int - var w: Int - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - var v: Int := 3 - z,w := exampleMult(v,x) - } -} - - -method exampleIncomplete(b:Bool,c:Int) returns (z:Int, w:Int) -influenced w by {b} -{ - z := 3 - if (b) { - var y: Int := 2 - w := y + 1 - } -} - -method mMethodCallOK2() -{ - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - var y:Int - var v:Int - var bool:Bool := true - var count: Int := 16 - y,v := exampleIncomplete(bool,count) - } -} - -method exampleHeap(b:Int) returns (c:Int) -influenced heap by {heap} -influenced c by {} -{ - c := 3 -} - - -method mMethodCallOK3() -{ - var z: Int - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - z := exampleHeap(x) - } -} - - -method exampleincorrect(b:Bool,c:Int) returns (z:Int, w:Int) -//:: ExpectedOutput(consistency.error) -influenced w by {} -{ - z := 3 - if (b) { - var y: Int := 2 - w := y + 1 - } -} - -method exampleOverapprox(b:Bool,c:Int) returns (z:Int, w:Int) -influenced w by {b,c} -{ - z := 3 - if (b) { - var y: Int := 2 - w := y + 1 - } -} - - - - -method exampleHeapArg2(b:Ref) returns (c:Int) -requires acc(b.f) -influenced heap by { heap } -influenced c by { heap , b } -{ - c := b.f -} - -method exampleHeapArg3(b:Ref) returns (c:Int) -requires acc(b.f) -influenced heap by { b, heap } -influenced c by { b, heap } -{ - c := b.f -} - -method exampleHeapArg4(b:Ref) returns (c:Int) -requires acc(b.f) -influenced heap by { heap, b } -influenced c by { heap, b } -{ - c := b.f -} - -method exampleWrongInfluenced(a:Int) returns (c:Int) -//:: ExpectedOutput(consistency.error) -influenced c by {c} -{ - c := 0 -} - -//:: ExpectedOutput(consistency.error) -method l1() -isLemma -{ - var z:Int := 3 -} - -method l2() -decreases -isLemma -{ - var x:Int - //:: ExpectedOutput(consistency.error) - inhale(x == 0) -} - -//:: ExpectedOutput(consistency.error) -method l3() -isLemma -decreases * -{ - var x:Int := 0 - while (x>0) { - x := x+1 - } -} - -method l4() -isLemma -decreases -{ - var t: Int := 0 -} - -predicate foo(xs:Ref) -{ - acc(xs.f) -} - -method mFold() -{ - var z: Ref - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - assume acc(z.f) - fold foo(z) - } -} - -method mFoldNOK() -{ - var z: Ref - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - z.f := x - assume acc(z.f) - fold foo(z) - } -} - -method mOldCallOK() -{ - label l - oldCall[l](l3()) -} - -method mOldCallNOK() -{ - var x: Int := 0 - label l - x := 2 - //:: ExpectedOutput(consistency.error) - oldCall[l](exampleHeap(x)) -} - - - -method callLemma(x:Int) returns (y:Int, w:Int) - -isLemma -decreases -requires x > 0 -ensures y + w + x > 7 -ensures y == 3 -{ - y := 3 - w := 4 -} - -method mOldCallOK1() -{ - var x: Int := 5 - label l - var z: Int - var a: Int - z,a := oldCall[l](callLemma(x + 5)) -} - - -method exProjDesc() -{ - var k: Int - //:: ExpectedOutput(consistency.error) - prove forall x: Int assuming true implies x==k { - k := x - } -} - - -method mWhileCheck() -{ - var c:Bool - var e:Int - var d:Int - //:: ExpectedOutput(consistency.error) - prove forall a:Int {P(a)} assuming P(a) implies Q(a) { - while (c) - { - d:=e - e:=a - } - } -} - - -method m(a:Int, b:Int) -returns (d:Int) -influenced d by {a} -influenced heap by {heap} -{ - d:=a -} - -method m1() -{ - var b:Int - var d:Int - //:: ExpectedOutput(consistency.error) - prove forall a:Int {P(a)} assuming P(a) implies Q(a) { - d:=m(a,b) - } -} - -method mInhaleAccess() -{ - var d:Ref - //:: ExpectedOutput(consistency.error) - prove forall a:Int {P(a)} assuming P(a) implies Q(a) { - inhale acc(d.f) - d.f:=a - } -} - - -method mLiteralAssign() -{ - var d: Int - prove forall x:Int {P(x)} assuming P(x) implies Q(x) - { - d:=x - d:=3 - } -} - -method mUnivIntro() -{ - var d: Int - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) - { - prove forall y:Int {P(y)} assuming P(y) implies Q(y) - { - d:=x - } - } -} - -method dosomething(x: Int) -{ - var y:Int := x -} - -method xmcall() -{ - //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) { - dosomething(x) - } -} diff --git a/src/test/resources/reasoning/universal_introduction/ui_assignments.vpr b/src/test/resources/reasoning/universal_introduction/ui_assignments.vpr deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/test/resources/reasoning/universal_introduction/ui_heap.vpr b/src/test/resources/reasoning/universal_introduction/ui_heap.vpr index 48b61f4ee..c47c02fa4 100644 --- a/src/test/resources/reasoning/universal_introduction/ui_heap.vpr +++ b/src/test/resources/reasoning/universal_introduction/ui_heap.vpr @@ -63,4 +63,30 @@ requires acc(y.f) prove forall x: Int {P(x)} assuming P(x) implies Q(x) && z >= 0 || z < 0 { z := y.f } +} + +method quasiHavocOk1(y:Ref) +{ + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && (y.f > 0 || y.f <= 0) { + quasihavoc y.f + } +} + + +method quasiHavocNOk1(y:Ref) +{ + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && (y.f > 0 || y.f <= 0) { + quasihavoc x == 10 ==> y.f + } +} + +method quasiHavocNOk2(y:Ref) +{ + //:: ExpectedOutput(consistency.error) + prove forall x:Int {P(x)} assuming P(x) implies Q(x) && (y.f > 0 || y.f <= 0) { + if(x == 100) { + quasihavoc y.f + } + } } \ No newline at end of file diff --git a/src/test/resources/reasoning/universal_introduction/ui_methods.vpr b/src/test/resources/reasoning/universal_introduction/ui_methods.vpr index 3d425e493..b8539fa02 100644 --- a/src/test/resources/reasoning/universal_introduction/ui_methods.vpr +++ b/src/test/resources/reasoning/universal_introduction/ui_methods.vpr @@ -32,7 +32,7 @@ method mMethodCallNOK1() method mMethodCallNOK2() { var z: Int - //:: ExpectedOutput(consistency.error) + // This should be OK, since the return value in example2 does not depend on its arguments prove forall x:Int {P(x)} assuming P(x) implies Q(x) && z == 0 { z := example2(x,x) } @@ -179,22 +179,21 @@ method test5(x: Int, y: Int) returns (a: Int, b: Int) b := x + y } +// Both specifications are over approximated, the heap is not needed method test5_equivalent(x: Int, y: Int) returns (a: Int, b: Int) +//:: ExpectedOutput(consistency.error) influenced a by {y, x, heap} +//:: ExpectedOutput(consistency.error) influenced b by {x, heap, y} { a := x + y b := x + y } -// The annotation "influenced heap by {heap}" should be rejected, -// since test5 has no flow-annotation for the heap, thus it should be -// considered as influenced heap by {heap, x, y} method test5_caller(x: Int, y: Int) returns (a: Int, b: Int) -//:: ExpectedOutput(consistency.error) influenced heap by {heap} - influenced a by {y, x, heap} - influenced b by {x, heap, y} + influenced a by {y, x} + influenced b by {x, y} { a, b := test5(x, y) } From 8e1efab7a1db03fa70c09b7c66a188aba8520e97 Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Thu, 18 Apr 2024 08:57:33 +0200 Subject: [PATCH 20/41] remove logs --- .../standard/reasoning/analysis/VarAnalysisGraphMap.scala | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index 9c3b525ad..ad5c23194 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -293,11 +293,6 @@ case class VarAnalysisGraphMap(prog: Program, // We set the position to the method call instead of the assume statement, so potential error are more readable. assumeAnalysis.addAll(methodAssumeAnalysis.map(v => pos -> (v._2.flatMap(v => methodArgExpMapping(v)) ++ pathInfluencingVars))) - - logger.warn("{}", methodAnalysisMap(method)) - logger.warn("{}", retVarMapping) - - logger.warn("{}", resolvedMethodMap) graphMap ++ resolvedMethodMap } From 191a8123677447d49fa97a3de8086b40fef5277f Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Thu, 18 Apr 2024 09:51:31 +0200 Subject: [PATCH 21/41] remove todo --- .../plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index ad5c23194..23fe47bb8 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -183,7 +183,6 @@ case class VarAnalysisGraphMap(prog: Program, val graph = s match { case Seqn(ss, _) => ss.foldLeft(scopedGraph) { (graph, subStmt) => computeInfluenceMap(subStmt, graph, pathInfluencingVariables) } - // TODO Why is OldCall a Scope statement? case o: OldCall => val met = prog.findMethod(o.methodName) computeMethodInfluenceMap(graphMap, met, o.args, o.rets, pathInfluencingVariables, o.pos) From 04de8bc1a606b3d7d864ead2f7983a0e386e2eee Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Thu, 18 Apr 2024 09:52:14 +0200 Subject: [PATCH 22/41] add missing position information to ImpureAssumeRewriter --- .../scala/viper/silver/ast/utility/ImpureAssumeRewriter.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/viper/silver/ast/utility/ImpureAssumeRewriter.scala b/src/main/scala/viper/silver/ast/utility/ImpureAssumeRewriter.scala index 39381eb8b..df062080c 100644 --- a/src/main/scala/viper/silver/ast/utility/ImpureAssumeRewriter.scala +++ b/src/main/scala/viper/silver/ast/utility/ImpureAssumeRewriter.scala @@ -133,7 +133,7 @@ object ImpureAssumeRewriter { assert(conds.isEmpty) assert(cond.isEmpty) - PermGeCmp(permLoc, perm)() + PermGeCmp(permLoc, perm)(permLoc.pos, permLoc.info, permLoc.errT) } else { val perms: Seq[Exp] = (contextWithoutRcv map (_._2)) :+ perm @@ -146,7 +146,7 @@ object ImpureAssumeRewriter { val func = funcs(contextWithoutRcv.length-1) val funcApp = DomainFuncApp(func, conds ++ perms, Map[TypeVar, Type]())() - PermGeCmp(permLoc, funcApp)() + PermGeCmp(permLoc, funcApp)(permLoc.pos, permLoc.info, permLoc.errT) } } From f79936a807a0d4eaace19718c7930c7ea51204d9 Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Mon, 29 Apr 2024 09:44:18 +0200 Subject: [PATCH 23/41] fix method analysis modularity --- .../standard/reasoning/ReasoningPlugin.scala | 5 +++++ .../analysis/VarAnalysisGraphMap.scala | 22 ++++++++++++++----- .../universal_introduction/ui_assume.vpr | 19 ++++++++++++++++ .../universal_introduction/ui_methods.vpr | 5 ++--- 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index 31cf512e3..4ecdb44f8 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -133,6 +133,11 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, /** create graph with vars that are in scope only outside of the universal introduction code block including the qunatified variables */ val analysis: VarAnalysisGraphMap = VarAnalysisGraphMap(input, logger, reportError) + /** Run taint analysis for all methods annotated with influences */ + input.methods.filter(m => m.posts.collect({ case i: FlowAnnotation => i }).nonEmpty).foreach( + method => analysis.executeTaintedGraphMethodAnalysis(method) + ) + val newAst: Program = ViperStrategy.Slim({ /** remove the influenced by postconditions. diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index 23fe47bb8..d9a88039c 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -99,9 +99,9 @@ case class VarAnalysisGraphMap(prog: Program, * If a recursive method is encountered, we fall back to the specified influences or a over approximation for the graph map * and an over approximation for the assume analysis */ - private def executeTaintedGraphMethodAnalysis(method: Method): Unit = { + def executeTaintedGraphMethodAnalysis(method: Method): Unit = { if(methodAnalysisStarted.contains(method) || method.body.isEmpty) { - if(methodAnalysisStarted.contains(method)) { + if(methodAnalysisStarted.contains(method) && !methodReturnInfluencesFullySpecified(method)) { logger.warn(s"Taint analysis does not support recursive method calls. Falling back to specified influences. (${method.name} ${method.pos})") } methodAnalysisMap.put(method, getDefaultMethodInfluences(method)) @@ -116,7 +116,7 @@ case class VarAnalysisGraphMap(prog: Program, }))(method.body.get.pos, method.body.get.info, method.body.get.errT) val assumeAnalysis: AssumeAnalysis = mutable.Map() - val map = computeInfluenceMap(stmt, initialGraph, Set())(assumeAnalysis) + var map = computeInfluenceMap(stmt, initialGraph, Set())(assumeAnalysis) if(!methodAnalysisMap.contains(method)) { // Check calculated value against the provided specification if there are any @@ -124,9 +124,15 @@ case class VarAnalysisGraphMap(prog: Program, val returnVar = AnalysisUtils.getLocalVarDeclFromFlowVar(f.v) val specifiedInfluences = f.varList.map(AnalysisUtils.getLocalVarDeclFromFlowVar).toSet val calculatedInfluences = lookupVar(returnVar, map) - if (calculatedInfluences != specifiedInfluences) { - reportErrorWithMsg(ConsistencyError(s"Specified influence on return variable $returnVar differs from calculated value. Specified: $specifiedInfluences Calculated: $calculatedInfluences", f.pos)) + + if (!calculatedInfluences.subsetOf(specifiedInfluences)) { + reportErrorWithMsg(ConsistencyError(s"Specified influence on return variable $returnVar is missing some potential influences. Specified: $specifiedInfluences Calculated: $calculatedInfluences", f.pos)) + } + + if (calculatedInfluences.intersect(specifiedInfluences).size < calculatedInfluences.size) { + logger.warn(s"Specified influence on return variable $returnVar potentially assumes too many influences. Specified: $specifiedInfluences Calculated: $calculatedInfluences, (${f.pos})") } + map = map + (returnVar -> specifiedInfluences) }) methodAnalysisMap.put(method, map) @@ -136,6 +142,12 @@ case class VarAnalysisGraphMap(prog: Program, methodAnalysisStarted -= method } + private def methodReturnInfluencesFullySpecified(method: Method): Boolean = { + val allVars = method.posts.collect({ case f: FlowAnnotation => f.v }) + val vars = allVars.collect({ case Var(localVar) => getLocalVarDeclFromLocalVar(localVar)}) + method.formalReturns.map(v => vars.contains(v)).forall(b => b) && allVars.collect({ case h: Heap => h}).nonEmpty + } + /** * Fallback for the influence analysis of methods. If influences for a return variable (including the heap) are declared, they are trusted. * Otherwise we over approximate by assuming every method arg (including the heap) influences the return variable. diff --git a/src/test/resources/reasoning/universal_introduction/ui_assume.vpr b/src/test/resources/reasoning/universal_introduction/ui_assume.vpr index 487d637b5..0683d4398 100644 --- a/src/test/resources/reasoning/universal_introduction/ui_assume.vpr +++ b/src/test/resources/reasoning/universal_introduction/ui_assume.vpr @@ -83,6 +83,25 @@ method assumeMultipleNestedF() } } +method assumeMultipleNestedOuterInfluenceF() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + prove forall y: Int {P(y)} assuming P(y) implies Q(y) { + //:: ExpectedOutput(consistency.error) + assume(1==x) + + if(1==x) { + //:: ExpectedOutput(consistency.error) + assume(true) + } + while(1 > x) { + //:: ExpectedOutput(consistency.error) + assume(true) + } + } + } +} + method assumeS() { var y: Int := 0 diff --git a/src/test/resources/reasoning/universal_introduction/ui_methods.vpr b/src/test/resources/reasoning/universal_introduction/ui_methods.vpr index b8539fa02..de08a7fcb 100644 --- a/src/test/resources/reasoning/universal_introduction/ui_methods.vpr +++ b/src/test/resources/reasoning/universal_introduction/ui_methods.vpr @@ -179,11 +179,9 @@ method test5(x: Int, y: Int) returns (a: Int, b: Int) b := x + y } -// Both specifications are over approximated, the heap is not needed +// Both specifications are over approximated, the heap is not needed. An error is not expected, but a warning is logged method test5_equivalent(x: Int, y: Int) returns (a: Int, b: Int) -//:: ExpectedOutput(consistency.error) influenced a by {y, x, heap} -//:: ExpectedOutput(consistency.error) influenced b by {x, heap, y} { a := x + y @@ -201,6 +199,7 @@ method test5_caller(x: Int, y: Int) returns (a: Int, b: Int) // should be rejected (at most 1 line per return variable) method test6(x: Int, y: Int) returns (a: Int, b: Int) influenced a by {y, x} +//:: ExpectedOutput(consistency.error) influenced b by { } //:: ExpectedOutput(consistency.error) influenced b by {x, y} From 6d6179c75c3d9a29b8e922045b6a0830715b4d44 Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Tue, 21 May 2024 09:14:50 +0200 Subject: [PATCH 24/41] add assumesNothing spec to specify that a method contains no assume/inhale statements --- .../reasoning/BeforeVerifyHelper.scala | 62 +---- .../reasoning/ReasoningASTExtension.scala | 25 ++ .../reasoning/ReasoningPASTExtension.scala | 27 +- .../standard/reasoning/ReasoningPlugin.scala | 18 +- .../analysis/VarAnalysisGraphMap.scala | 240 ++++++++++-------- .../ui_assume_methods.vpr | 152 +++++++++++ .../universal_introduction/ui_methods.vpr | 24 +- 7 files changed, 394 insertions(+), 154 deletions(-) create mode 100644 src/test/resources/reasoning/universal_introduction/ui_assume_methods.vpr diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala index 27e478ce0..9967058f8 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala @@ -8,8 +8,7 @@ package viper.silver.plugin.standard.reasoning import viper.silver.ast.utility.Expressions -import viper.silver.ast.{Apply, Exhale, Exp, FieldAssign, Fold, Inhale, LocalVarDecl, Method, MethodCall, Package, Program, Seqn, Stmt, Unfold} -import viper.silver.plugin.standard.termination.{DecreasesSpecification, DecreasesStar, DecreasesTuple, DecreasesWildcard} +import viper.silver.ast.{Apply, Declaration, Exhale, Exp, FieldAssign, Fold, Inhale, LocalVarDecl, Method, MethodCall, Package, Program, Seqn, Stmt, Unfold} import viper.silver.verifier.{AbstractError, ConsistencyError} import viper.silver.plugin.standard.reasoning.analysis.AnalysisUtils @@ -70,56 +69,23 @@ trait BeforeVerifyHelper { def checkLemma(input: Program, reportError: AbstractError => Unit): Unit = { input.methods.foreach(method => { val containsLemma = specifiesLemma(method) - var containsDecreases = false + val (containsDecreases, containsDecreasesStar) = AnalysisUtils.containsDecreasesAnnotations(method) + if (containsLemma) { - /** check preconditions for decreases clause */ - method.pres.foreach { - case DecreasesTuple(_, _) => - containsDecreases = true - case DecreasesWildcard(_) => - containsDecreases = true - case DecreasesStar() => - reportError(ConsistencyError("decreases statement might not prove termination", method.pos)) - case _ => - () - } - /** check postconditions for decreases clause */ - method.posts.foreach { - case DecreasesTuple(_, _) => - containsDecreases = true - case DecreasesWildcard(_) => - containsDecreases = true - case DecreasesStar() => - reportError(ConsistencyError("decreases statement might not prove termination", method.pos)) - containsDecreases = true - case _ => () + /** report error if there is no decreases clause or specification */ + if(!containsDecreases) { + reportError(ConsistencyError(s"method ${method.name} marked lemma might not contain decreases clause", method.pos)) } - /** check info for decreases specification */ - method.meta._2 match { - case spec: DecreasesSpecification => - if (spec.star.isDefined) { - reportError(ConsistencyError("decreases statement might not prove termination", method.pos)) - containsDecreases = true - } else { - containsDecreases = true - } - case _ => + /** report error if the decreases statement might not prove termination */ + if (containsDecreasesStar) { + reportError(ConsistencyError("decreases statement might not prove termination", method.pos)) } - } - /** report error if there is no decreases clause or specification */ - if (containsLemma && !containsDecreases) { - reportError(ConsistencyError(s"method ${method.name} marked lemma might not contain decreases clause", method.pos)) - } - - /** check method body for impure statements */ - if (containsLemma) { + /** check method body for impure statements */ checkBodyPure(method.body.getOrElse(Seqn(Seq(), Seq())()), method, input, reportError) } - }) - } /** checks whether the body is pure, reports error if impure operation found */ @@ -155,14 +121,14 @@ trait BeforeVerifyHelper { /** checks that influences by annotations are used correctly. */ def checkInfluencedBy(input: Program, reportError: AbstractError => Unit): Unit = { input.methods.foreach(method => { - val argVars = method.formalArgs ++ AnalysisUtils.heapVertex - val retVars = method.formalReturns ++ AnalysisUtils.heapVertex + val argVars = method.formalArgs.toSet + AnalysisUtils.heapVertex + val retVars = method.formalReturns.toSet.asInstanceOf[Set[Declaration]] + AnalysisUtils.heapVertex + AnalysisUtils.AssumeNode(method.pos) - val seenVars: mutable.Set[LocalVarDecl] = mutable.Set() + val seenVars: mutable.Set[Declaration] = mutable.Set() /** iterate through method postconditions to find flow annotations */ method.posts.foreach { case v@FlowAnnotation(target, args) => - val targetVarDecl = AnalysisUtils.getLocalVarDeclFromFlowVar(target) + val targetVarDecl = AnalysisUtils.getDeclarationFromFlowVar(target, method) if (!retVars.contains(targetVarDecl)) { reportError(ConsistencyError(s"Only return variables can be influenced. ${targetVarDecl.name} is not be a return variable.", v.pos)) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala index 166e8d6a8..45d259468 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala @@ -61,6 +61,12 @@ sealed trait FlowVar extends ExtensionExp { } } +case class Assumes()(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowVar { + override val extensionSubnodes: Seq[Node] = Seq.empty + override def typ: Type = InternalType + override def prettyPrint: PrettyPrintPrimitives#Cont = PAssumesKeyword.keyword +} + case class Heap()(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowVar { override val extensionSubnodes: Seq[Node] = Seq.empty override def typ: Type = InternalType @@ -73,6 +79,25 @@ case class Var(decl: LocalVar)(val pos: Position = NoPosition, val info: Info = override def prettyPrint: PrettyPrintPrimitives#Cont = show(decl) } +case class NoAssumeAnnotation()(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionExp with Node { + override def extensionIsPure: Boolean = true + + override def extensionSubnodes: Seq[Node] = Seq() + + override def typ: Type = Bool + + override def verifyExtExp(): VerificationResult = { + assert(assertion = false, "FlowAnalysis: verifyExtExp has not been implemented.") + Failure(Seq(ConsistencyError("FlowAnalysis: verifyExtExp has not been implemented.", pos))) + } + + /** Pretty printing functionality as defined for other nodes in class FastPrettyPrinter. + * Sample implementation would be text("old") <> parens(show(e)) for pretty-printing an old-expression. */ + override def prettyPrint: PrettyPrintPrimitives#Cont = { + text("assumes nothing") + } +} + case class FlowAnnotation(v: FlowVar, varList: Seq[FlowVar])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionExp with Node with Scope { override def extensionIsPure: Boolean = true diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala index cb1553576..97bccabb3 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -7,7 +7,7 @@ package viper.silver.plugin.standard.reasoning import viper.silver.FastMessaging -import viper.silver.ast.{ExtensionExp, LocalVar, LocalVarDecl, Position, Seqn, Stmt, Trigger} +import viper.silver.ast.{Exp, ExtensionExp, LocalVar, LocalVarDecl, Position, Seqn, Stmt, Trigger} import viper.silver.parser.TypeHelper.Bool import viper.silver.parser.{NameAnalyser, PAssignTarget, PCall, PCallable, PDelimited, PExp, PExtender, PGrouped, PIdnRef, PIdnUseExp, PKeywordLang, PKeywordStmt, PKw, PLabel, PLocalVarDecl, PReserved, PSeqn, PStmt, PSym, PSymOp, PTrigger, PType, PTypeSubstitution, Translator, TypeChecker} @@ -19,6 +19,8 @@ case object PImpliesKeyword extends PKw("implies") with PKeywordLang case object PInfluencedKeyword extends PKw("influenced") with PKeywordLang with PKw.PostSpec case object PByKeyword extends PKw("by") with PKeywordLang case object PHeapKeyword extends PKw("heap") with PKeywordLang +case object PAssumesKeyword extends PKw("assumes") with PKeywordLang +case object PNothingKeyword extends PKw("assumesNothing") with PKeywordLang with PKw.PostSpec case object PIsLemmaKeyword extends PKw("isLemma") with PKeywordLang with PKw.PreSpec with PKw.PostSpec case object POldCallKeyword extends PKw("oldCall") with PKeywordLang with PKeywordStmt @@ -111,6 +113,29 @@ case class PHeap(heap: PReserved[PHeapKeyword.type])(val pos: (Position,Position override def pretty: String = PHeapKeyword.keyword } +case class PAssumes(assumes: PReserved[PAssumesKeyword.type])(val pos: (Position,Position)) extends PFlowVar { + override def translate(t: Translator): Assumes = { + Assumes()(t.liftPos(this)) + } + + override def pretty: String = PAssumesKeyword.keyword +} + +case class PAssumesNothing()(val pos: (Position,Position)) extends PExp with PExtender { + def translate(t: Translator): NoAssumeAnnotation = { + NoAssumeAnnotation()(t.liftPos(this)) + } + + override def pretty: String = PAssumesKeyword.keyword + override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = None + + override def translateExp(t: Translator): Exp = NoAssumeAnnotation()(pos._1) + + override def typeSubstitutions: collection.Seq[PTypeSubstitution] = Seq(PTypeSubstitution.id) + + override def forceSubstitution(ts: PTypeSubstitution): Unit = {} +} + case class PVar(decl: PIdnUseExp)(val pos: (Position,Position)) extends PFlowVar { override def translate(t: Translator): Var = { // due to the implementation of `t.exp`, a LocalVar should be returned diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index 4ecdb44f8..2c361e6c8 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -12,7 +12,7 @@ import viper.silver.ast.utility.rewriter.{StrategyBuilder, Traverse} import viper.silver.ast.utility.ViperStrategy import viper.silver.parser.FastParserCompanion.whitespace import viper.silver.parser._ -import viper.silver.plugin.standard.reasoning.analysis.{AnalysisUtils, VarAnalysisGraphMap} +import viper.silver.plugin.standard.reasoning.analysis.VarAnalysisGraphMap import viper.silver.plugin.{ParserPluginTemplate, SilverPlugin} import viper.silver.verifier._ @@ -45,10 +45,17 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, def heap[$: P]: P[PHeap] = P(P(PHeapKeyword) map (PHeap(_) _)).pos // note that the parentheses are not redundant + def assumes[$: P]: P[PAssumes] = P(P(PAssumesKeyword) map (PAssumes(_) _)).pos // note that the parentheses are not redundant + + def assumesNothingSpec[$: P]: P[PSpecification[PNothingKeyword.type]] = + P(P(PNothingKeyword) map { + case (b) => PSpecification(b, PAssumesNothing()(b.pos))(_) + }).pos + def singleVar[$: P]: P[PVar] = P(fp.idnuse map (PVar(_) _)).pos // note that the parentheses are not redundant def varsAndHeap[$: P]: P[Seq[PFlowVar]] = (heap | singleVar).delimited(PSym.Comma).map(_.toSeq) - def influencedBy[$: P]: P[PFlowAnnotation] = P(((heap | singleVar) ~ P(PByKeyword) ~/ varsAndHeap.braces) map { + def influencedBy[$: P]: P[PFlowAnnotation] = P(((heap | assumes | singleVar) ~ P(PByKeyword) ~/ varsAndHeap.braces) map { case (v, byKw, groupedVarList) => PFlowAnnotation(v, byKw, groupedVarList)(_) }).pos @@ -97,6 +104,7 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, /** add influenced by flow annotation to as a postcondition */ ParserExtension.addNewPostCondition(flowSpec(_)) + ParserExtension.addNewPostCondition(assumesNothingSpec(_)) /** add lemma as an annotation either as a pre- or a postcondition */ ParserExtension.addNewPreCondition(lemma(_)) @@ -256,12 +264,14 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, /** Get all variables that are in scope in the current method */ val tainted: Set[LocalVarDecl] = v.toSet - val varsOutside = (input.methods.flatMap(m => m.body.get.ss.filter(s => s.contains(u)).flatMap(_ => Set(m.transitiveScopedDecls: _*))).toSet -- Set(u.transitiveScopedDecls: _*)) ++ tainted + val varsOutside = (input.methods + .filter(m => m.body.isDefined) + .flatMap(m => m.body.get.ss.filter(s => s.contains(u)).flatMap(_ => Set(m.transitiveScopedDecls: _*))).toSet -- Set(u.transitiveScopedDecls: _*)) ++ tainted /** * get all variables that are assigned to inside the block and take intersection with universal introduction * variables. If they are contained throw error since quantified variables should be immutable */ - val writtenVars: Set[LocalVarDecl] = AnalysisUtils.getModifiedVars(blk) + val writtenVars: Set[LocalVarDecl] = analysis.getModifiedVars(blk).collect({ case v: LocalVarDecl => v}) checkReassigned(writtenVars, v, reportError, u) checkInfluencedBy(input, reportError) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index d9a88039c..59cb3859c 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -7,62 +7,78 @@ package viper.silver.plugin.standard.reasoning.analysis import viper.silver.ast.utility.Expressions -import viper.silver.ast.{AccessPredicate, Apply, Assert, Assume, BinExp, CurrentPerm, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, Fold, ForPerm, FuncApp, Goto, If, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, Method, MethodCall, Package, Position, Program, Quasihavoc, Quasihavocall, Ref, Scope, Seqn, Stmt, UnExp, Unfold, While} -import viper.silver.plugin.standard.reasoning.analysis.AnalysisUtils.{getLocalVarDeclFromLocalVar, heapVertex} -import viper.silver.plugin.standard.reasoning.{ExistentialElim, FlowAnnotation, FlowVar, Heap, OldCall, UniversalIntro, Var} +import viper.silver.ast.{AccessPredicate, Apply, Assert, Assume, BinExp, CurrentPerm, Declaration, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, Fold, ForPerm, FuncApp, Goto, If, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, Method, MethodCall, Package, Position, Program, Quasihavoc, Quasihavocall, Ref, Scope, Seqn, Stmt, UnExp, Unfold, While} +import viper.silver.plugin.standard.reasoning.analysis.AnalysisUtils.{AssumeNode, getLocalVarDeclFromLocalVar, heapVertex} +import viper.silver.plugin.standard.reasoning.{Assumes, ExistentialElim, FlowAnnotation, FlowVar, Heap, NoAssumeAnnotation, OldCall, UniversalIntro, Var} +import viper.silver.plugin.standard.termination.{DecreasesSpecification, DecreasesStar, DecreasesTuple, DecreasesWildcard} import viper.silver.verifier.{AbstractError, ConsistencyError} import scala.collection.mutable object AnalysisUtils { + + // TODO Add NoAssume Specification (default for all cases except for non-terminating abstract methods (e.g AssumeFree). + // Also check method Body for assumes (or inhales in case this specification is present + case class AssumeNode(pos: Position) extends Declaration { + override def name: String = ".assume" + } + val heapVertex: LocalVarDecl = LocalVarDecl(".heap", Ref)() - def getLocalVarDeclFromFlowVar(f: FlowVar): LocalVarDecl = { + def getDeclarationFromFlowVar(f: FlowVar, m: Method): Declaration = { f match { case v: Var => LocalVarDecl(v.decl.name, v.decl.typ)(v.decl.pos) case _: Heap => heapVertex + case _: Assumes => AssumeNode(m.pos) } } - /** - * get the variables that were modified by the statement stmt - */ - def getModifiedVars(stmt: Stmt): Set[LocalVarDecl] = { - stmt match { - case Seqn(ss, _) => ss.flatMap(s => getModifiedVars(s)).toSet - case LocalVarAssign(l, _) => Set(getLocalVarDeclFromLocalVar(l)) - case If(_, thn, els) => getModifiedVars(thn) ++ getModifiedVars(els) - case While(_, _, body) => getModifiedVars(body) - case MethodCall(_, _, targets) => targets.map(getLocalVarDeclFromLocalVar).toSet - case Inhale(_) => Set() - case Assume(_) => Set() - case Label(_, _) => Set() - case Quasihavoc(_, _) => Set(heapVertex) - case Quasihavocall(_, _, _) => Set(heapVertex) - case ExistentialElim(vars, _, _) => vars.toSet - case _ => Set() + def getLocalVarDeclFromFlowVar(f: FlowVar): LocalVarDecl = { + f match { + case v: Var => LocalVarDecl(v.decl.name, v.decl.typ)(v.decl.pos) + case _: Heap => heapVertex } } def getLocalVarDeclFromLocalVar(l: LocalVar): LocalVarDecl = { LocalVarDecl(l.name, l.typ)() } -} + def containsDecreasesAnnotations(m: Method): (Boolean, Boolean) = { + val presContain = m.pres.collect({ case DecreasesTuple(_, _) | DecreasesWildcard(_) | DecreasesStar() => true }).nonEmpty + val postsContain = m.posts.collect({ case DecreasesTuple(_, _) | DecreasesWildcard(_) | DecreasesStar() => true }).nonEmpty + + val presContainStar = m.pres.collect({ case DecreasesStar() => true }).nonEmpty + val postsContainStar = m.posts.collect({ case DecreasesStar() => true }).nonEmpty + + + /** check info for decreases specification */ + val infoContains = m.meta._2 match { + case _: DecreasesSpecification => true + case _ => false + } + + val infoContainsStar = m.meta._2 match { + case spec: DecreasesSpecification => spec.star.isDefined + case _ => false + } + + val containsTerminationMeasure = presContain | postsContain | presContainStar | postsContainStar | infoContains + val mightNotTerminate = !containsTerminationMeasure | presContainStar | postsContainStar | infoContainsStar + + (containsTerminationMeasure, mightNotTerminate) + } +} case class VarAnalysisGraphMap(prog: Program, logger: ch.qos.logback.classic.Logger, reportErrorWithMsg: AbstractError => Unit) { // Maps all influences for a given variable - private type GraphMap = Map[LocalVarDecl, Set[LocalVarDecl]] - // Maps all influences for a statement at a given position - private type AssumeAnalysis = mutable.Map[Position, Set[LocalVarDecl]] - + private type GraphMap = Map[Declaration, Set[LocalVarDecl]] // Storage for method analysis private val methodAnalysisMap: mutable.Map[Method, GraphMap] = mutable.Map() - private val methodAssumeAnalysisMap: mutable.Map[Method, AssumeAnalysis] = mutable.Map() private val methodAnalysisStarted: mutable.ListBuffer[Method] = mutable.ListBuffer() @@ -70,26 +86,18 @@ case class VarAnalysisGraphMap(prog: Program, * When executed on the universal introduction statement the tainted variables are simply the quantified variables */ def executeTaintedGraphAnalysis(allVars: Set[LocalVarDecl], tainted: Set[LocalVarDecl], blk: Seqn, volatileVars: Set[LocalVarDecl], u: UniversalIntro): Unit = { // Build initial graph where every variable and the heap influences itself - val initialGraph = allVars.map(k => k -> Set(k)).toMap + (AnalysisUtils.heapVertex -> Set(AnalysisUtils.heapVertex)) - val assumeAnalysis: AssumeAnalysis = mutable.Map() - val graph = computeInfluenceMap(blk, initialGraph, Set())(assumeAnalysis) + val initialGraph = allVars.map(k => k.asInstanceOf[Declaration] -> Set(k)).toMap + (AnalysisUtils.heapVertex -> Set(AnalysisUtils.heapVertex)) + val graph = computeInfluenceMap(blk, initialGraph) val problems = volatileVars.filter(v => graph(v).intersect(tainted).nonEmpty) if(problems.nonEmpty) { reportErrorWithMsg(ConsistencyError("Universal introduction variable might have been assigned to variable " + problems + ", used in the quantified implication", u.pos)) } - assumeAnalysis.foreach(v => { - val assumeVars = v._2.intersect(tainted) - if (assumeVars.nonEmpty) { - val vars = assumeVars.map(v => s"'${v.name}'").mkString(", ") - val err = if (assumeVars.size == 1) { - "variable " + vars - } else { - "variables " + vars - } - reportErrorWithMsg(ConsistencyError(s"Tainted $err (${u.pos}) might influence assume statement", v._1)) - } - }) + + val assumeProblems = graph.keySet.filter(v => v.isInstanceOf[AssumeNode] && graph(v).intersect(tainted).nonEmpty) + assumeProblems.foreach(v => + reportErrorWithMsg(ConsistencyError(s"Universal introduction variable might influence assume statement", v.pos)) + ) } /** @@ -100,29 +108,40 @@ case class VarAnalysisGraphMap(prog: Program, * and an over approximation for the assume analysis */ def executeTaintedGraphMethodAnalysis(method: Method): Unit = { - if(methodAnalysisStarted.contains(method) || method.body.isEmpty) { - if(methodAnalysisStarted.contains(method) && !methodReturnInfluencesFullySpecified(method)) { + if (method.body.isEmpty) { + // Case for abstract methods + val map = getDefaultMethodInfluences(method) + logger.warn(s"${method.name}: $map") + methodAnalysisMap.put(method, map) + } else if(methodAnalysisStarted.contains(method)) { + // Case for recursive methods + if(!methodReturnInfluencesFullySpecified(method)) { logger.warn(s"Taint analysis does not support recursive method calls. Falling back to specified influences. (${method.name} ${method.pos})") } methodAnalysisMap.put(method, getDefaultMethodInfluences(method)) - methodAssumeAnalysisMap.put(method, overApproximateAssumeInfluences(method)) } else { + // Default case methodAnalysisStarted.addOne(method) - val initialGraph: GraphMap = (method.formalArgs.map(k => k -> Set(k)) ++ method.formalReturns.map(k => k -> Set[LocalVarDecl]())).toMap + (AnalysisUtils.heapVertex -> Set(AnalysisUtils.heapVertex)) + val initialGraph: GraphMap = (method.formalArgs.map(k => k.asInstanceOf[Declaration] -> Set(k)) ++ method.formalReturns.map(k => k -> Set[LocalVarDecl]())).toMap + (AnalysisUtils.heapVertex -> Set(AnalysisUtils.heapVertex)) val stmt = Seqn(method.body.get.ss, method.body.get.scopedSeqnDeclarations.filter({ case l: LocalVarDecl => !initialGraph.contains(l) case _ => true }))(method.body.get.pos, method.body.get.info, method.body.get.errT) - val assumeAnalysis: AssumeAnalysis = mutable.Map() - var map = computeInfluenceMap(stmt, initialGraph, Set())(assumeAnalysis) + var map = computeInfluenceMap(stmt, initialGraph) + val assumeVars = map.keySet.filter(v => v.isInstanceOf[AssumeNode]) + + // Remove all assume nodes and save them as a single assume node for the whole method + if(assumeVars.nonEmpty) { + map = map.removedAll(assumeVars) + (AssumeNode(method.pos) -> assumeVars.flatMap(v => map(v))) + } if(!methodAnalysisMap.contains(method)) { // Check calculated value against the provided specification if there are any method.posts.collect({ case f: FlowAnnotation => f }).foreach(f => { - val returnVar = AnalysisUtils.getLocalVarDeclFromFlowVar(f.v) - val specifiedInfluences = f.varList.map(AnalysisUtils.getLocalVarDeclFromFlowVar).toSet + val returnVar = AnalysisUtils.getDeclarationFromFlowVar(f.v, method) + val specifiedInfluences = f.varList.map(v => AnalysisUtils.getLocalVarDeclFromFlowVar(v)).toSet val calculatedInfluences = lookupVar(returnVar, map) if (!calculatedInfluences.subsetOf(specifiedInfluences)) { @@ -136,7 +155,6 @@ case class VarAnalysisGraphMap(prog: Program, }) methodAnalysisMap.put(method, map) - methodAssumeAnalysisMap.put(method, assumeAnalysis) } } methodAnalysisStarted -= method @@ -145,7 +163,7 @@ case class VarAnalysisGraphMap(prog: Program, private def methodReturnInfluencesFullySpecified(method: Method): Boolean = { val allVars = method.posts.collect({ case f: FlowAnnotation => f.v }) val vars = allVars.collect({ case Var(localVar) => getLocalVarDeclFromLocalVar(localVar)}) - method.formalReturns.map(v => vars.contains(v)).forall(b => b) && allVars.collect({ case h: Heap => h}).nonEmpty + method.formalReturns.map(v => vars.contains(v)).forall(b => b) && allVars.exists(v => v.isInstanceOf[Heap]) && allVars.exists(v => v.isInstanceOf[Assumes]) } /** @@ -153,40 +171,29 @@ case class VarAnalysisGraphMap(prog: Program, * Otherwise we over approximate by assuming every method arg (including the heap) influences the return variable. */ private def getDefaultMethodInfluences(method: Method): GraphMap = { - val retSet = method.formalReturns.toSet + AnalysisUtils.heapVertex + // We ignore the assume vertex on purpose here, as a missing assume vertex is treated as if no assume statement appears in the method + val retSet = method.formalReturns.toSet.asInstanceOf[Set[Declaration]] + AnalysisUtils.heapVertex val allMethodArgsSet = method.formalArgs.toSet + AnalysisUtils.heapVertex val annotationInfluences = method.posts.collect({ case FlowAnnotation(returned, arguments) => (returned, arguments) }).map(t => { - AnalysisUtils.getLocalVarDeclFromFlowVar(t._1) -> t._2.map(AnalysisUtils.getLocalVarDeclFromFlowVar).toSet + AnalysisUtils.getDeclarationFromFlowVar(t._1, method) -> t._2.map(f => AnalysisUtils.getLocalVarDeclFromFlowVar(f)).toSet }).toMap /** influence all return variables, not mentioned by annotation, by every method argument */ val otherInfluences = (retSet -- annotationInfluences.keySet).map(retVar => { retVar -> allMethodArgsSet }) - annotationInfluences ++ otherInfluences - } - - /** - * Over approximates the assume analysis, by assuming that the method's arguments influence every inhale statement - * that occurs in this method or in any transitively called method. - */ - private def overApproximateAssumeInfluences(method: Method): AssumeAnalysis = { - var allCalledMethods: Set[Method] = Set(method) - var nextSet: Set[Method] = allCalledMethods ++ getAllCalledMethods(method) + val (containsDecreases, containsDecreasesStar) = AnalysisUtils.containsDecreasesAnnotations(method) + val terminates = containsDecreases && !containsDecreasesStar + val noAssumes = method.pres.concat(method.posts).collect({case _: NoAssumeAnnotation => true}).nonEmpty - while (allCalledMethods != nextSet) { - allCalledMethods = nextSet - nextSet = allCalledMethods ++ allCalledMethods.flatMap(getAllCalledMethods) + if(annotationInfluences.contains(AssumeNode(method.pos)) || terminates || noAssumes) { + annotationInfluences ++ otherInfluences + } else { + annotationInfluences ++ otherInfluences + (AssumeNode(method.pos) -> allMethodArgsSet) } - - val allInhales = allCalledMethods.flatMap(met => met.deepCollectInBody({ case a: Inhale => a })) - val assumeAnalysis: AssumeAnalysis = mutable.Map() - assumeAnalysis.addAll(allInhales.map(i => i.pos -> method.formalArgs.toSet)) } - private def getAllCalledMethods(method: Method): Set[Method] = method.deepCollectInBody({ case a: MethodCall => a }).map(m => prog.findMethod(m.methodName)).toSet - - private def computeInfluenceMap(stmt: Stmt, graphMap: GraphMap, pathInfluencingVariables: Set[LocalVarDecl])(implicit assumeAnalysis: AssumeAnalysis): GraphMap = { + private def computeInfluenceMap(stmt: Stmt, graphMap: GraphMap): GraphMap = { stmt match { case s: Scope => // Temporarily add the scoped declarations to the graph and remove them afterwards @@ -194,15 +201,15 @@ case class VarAnalysisGraphMap(prog: Program, val scopedGraph = graphMap ++ declarations.map(decl => decl -> Set(decl)).toMap val graph = s match { case Seqn(ss, _) => - ss.foldLeft(scopedGraph) { (graph, subStmt) => computeInfluenceMap(subStmt, graph, pathInfluencingVariables) } + ss.foldLeft(scopedGraph) { (graph, subStmt) => computeInfluenceMap(subStmt, graph) } case o: OldCall => val met = prog.findMethod(o.methodName) - computeMethodInfluenceMap(graphMap, met, o.args, o.rets, pathInfluencingVariables, o.pos) + computeMethodInfluenceMap(graphMap, met, o.args, o.rets, o.pos) // The quantified variables of the Quasihavocall statement are ignored because they are untainted by definition case Quasihavocall(_, lhs, _) => val vars = lhs.map(e => getResolvedVarsFromExp(e, graphMap)).getOrElse(Set()) graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars)) - case u: UniversalIntro => computeInfluenceMap(u.block, scopedGraph, pathInfluencingVariables) + case u: UniversalIntro => computeInfluenceMap(u.block, scopedGraph) } graph.removedAll(declarations) case LocalVarAssign(lhs, rhs) => @@ -211,20 +218,20 @@ case class VarAnalysisGraphMap(prog: Program, case If(cond, thn, els) => val conditionVars = getResolvedVarsFromExp(cond, graphMap) // For the condition influences, we only care for variables that are declared outside of the if block - val writesInIfBlocks = (AnalysisUtils.getModifiedVars(thn) ++ AnalysisUtils.getModifiedVars(els)).filter(v => graphMap.contains(v)) + val writesInIfBlocks = (getModifiedVars(thn) ++ getModifiedVars(els)).filter(v => v.isInstanceOf[AssumeNode] || graphMap.contains(v)) val conditionInfluences = writesInIfBlocks.map(v => v -> (lookupVar(v, graphMap) ++ conditionVars.flatMap(c => lookupVar(c, graphMap)))).toMap - val thenGraph = computeInfluenceMap(thn, graphMap, pathInfluencingVariables ++ conditionVars) - val elseGraph = computeInfluenceMap(els, graphMap, pathInfluencingVariables ++ conditionVars) + val thenGraph = computeInfluenceMap(thn, graphMap) + val elseGraph = computeInfluenceMap(els, graphMap) (thenGraph.keySet ++ elseGraph.keySet).map( v => v -> (thenGraph.getOrElse(v, Set()) ++ elseGraph.getOrElse(v, Set()) ++ conditionInfluences.getOrElse(v, Set())) ).toMap case While(cond, _, body) => - var iterationGraph = computeInfluenceMap(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), graphMap, pathInfluencingVariables) + var iterationGraph = computeInfluenceMap(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), graphMap) var edgesEqual: Boolean = false var mergeGraph = iterationGraph while (!edgesEqual) { - iterationGraph = computeInfluenceMap(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), mergeGraph, pathInfluencingVariables) + iterationGraph = computeInfluenceMap(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), mergeGraph) if(iterationGraph.equals(mergeGraph)) { edgesEqual = true } else { @@ -234,7 +241,7 @@ case class VarAnalysisGraphMap(prog: Program, mergeGraph case m: MethodCall => val met = prog.findMethod(m.methodName) - computeMethodInfluenceMap(graphMap, met, m.args, m.targets, pathInfluencingVariables, m.pos) + computeMethodInfluenceMap(graphMap, met, m.args, m.targets, m.pos) case FieldAssign(_, rhs) => val vars = getResolvedVarsFromExp(rhs, graphMap) graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars.filter(v => !v.equals(AnalysisUtils.heapVertex)).flatMap(v => graphMap(v)))) @@ -264,9 +271,8 @@ case class VarAnalysisGraphMap(prog: Program, case Label(_, _) => graphMap // Assume analysis case a: Inhale => - val assumeVars = (pathInfluencingVariables ++ getResolvedVarsFromExp(a.exp, graphMap)).flatMap(v => lookupVar(v, graphMap) + v) - assumeAnalysis.addOne(a.pos -> assumeVars) - graphMap + val assumeVars = (lookupVar(AssumeNode(a.pos), graphMap) ++ getResolvedVarsFromExp(a.exp, graphMap)).flatMap(v => lookupVar(v, graphMap) + v) + graphMap + (AssumeNode(a.pos) -> assumeVars) case ExistentialElim(vars, _, exp) => graphMap ++ vars.map(v => v -> getResolvedVarsFromExp(exp, graphMap)) // Non handled cases case a: Assume => @@ -283,33 +289,67 @@ case class VarAnalysisGraphMap(prog: Program, /** creates graph for method call and old call. maps expressions passed to methods to the method arguments, computes the graph based on the flow annotation, * and finally maps the return variables to the variables that the method is assigned to. */ - private def computeMethodInfluenceMap(graphMap: GraphMap, method: Method, callArgs: Seq[Exp], callTargets: Seq[LocalVar], pathInfluencingVars: Set[LocalVarDecl], pos: Position)(implicit assumeAnalysis: AssumeAnalysis): GraphMap = { + private def computeMethodInfluenceMap(graphMap: GraphMap, method: Method, callArgs: Seq[Exp], callTargets: Seq[LocalVar], pos: Position): GraphMap = { /** set of all target variables that have not been included in the influenced by expression up until now */ val methodArgExpMapping = (method.formalArgs zip callArgs).map(methodArg => - methodArg._1 -> getResolvedVarsFromExp(methodArg._2, graphMap) + methodArg._1.asInstanceOf[Declaration] -> getResolvedVarsFromExp(methodArg._2, graphMap) ).toMap + (AnalysisUtils.heapVertex -> Set(AnalysisUtils.heapVertex)) - val retVarMapping = (callTargets.map(l => AnalysisUtils.getLocalVarDeclFromLocalVar(l)) zip method.formalReturns) - .map(vars => vars._2 -> vars._1).toMap + (AnalysisUtils.heapVertex -> AnalysisUtils.heapVertex) - if(!methodAnalysisMap.contains(method)) { - executeTaintedGraphMethodAnalysis(method) - } + val retVarMapping = (callTargets.map(l => AnalysisUtils.getLocalVarDeclFromLocalVar(l)) zip method.formalReturns) + .map(vars => vars._2.asInstanceOf[Declaration] -> vars._1.asInstanceOf[Declaration]).toMap + (AnalysisUtils.heapVertex -> AnalysisUtils.heapVertex) + (AssumeNode(method.pos) -> AssumeNode(pos)) - val resolvedMethodMap = methodAnalysisMap(method) + val resolvedMethodMap = getMethodAnalysisMap(method) .filter(v => retVarMapping.contains(v._1)) .map(v => retVarMapping(v._1) -> v._2.flatMap(methodArgExpMapping)) + (AnalysisUtils.heapVertex -> (graphMap(AnalysisUtils.heapVertex) ++ methodAnalysisMap(method)(AnalysisUtils.heapVertex).flatMap(methodArgExpMapping))) - val methodAssumeAnalysis = methodAssumeAnalysisMap(method) - + logger.warn(s"${method.name} resolved: $resolvedMethodMap") // We set the position to the method call instead of the assume statement, so potential error are more readable. - assumeAnalysis.addAll(methodAssumeAnalysis.map(v => pos -> (v._2.flatMap(v => methodArgExpMapping(v)) ++ pathInfluencingVars))) graphMap ++ resolvedMethodMap } - private def lookupVar(variable: LocalVarDecl, graphMap: GraphMap): Set[LocalVarDecl] = { - assert(graphMap.contains(variable), "Variable " + variable + " not found in graph analysis") - graphMap(variable) + private def getMethodAnalysisMap(method: Method): GraphMap = { + if (!methodAnalysisMap.contains(method)) { + executeTaintedGraphMethodAnalysis(method) + } + + methodAnalysisMap(method) + } + + /** + * get the variables that were modified by the statement stmt + */ + def getModifiedVars(stmt: Stmt): Set[Declaration] = { + stmt match { + case Seqn(ss, _) => ss.flatMap(s => getModifiedVars(s)).toSet + case LocalVarAssign(l, _) => Set(getLocalVarDeclFromLocalVar(l)) + case If(_, thn, els) => getModifiedVars(thn) ++ getModifiedVars(els) + case While(_, _, body) => getModifiedVars(body) + case MethodCall(name, _, targets) => + val methodInfluences = getMethodAnalysisMap(prog.methodsByName(name)) + if(methodInfluences.exists(v => v._1.isInstanceOf[AssumeNode])) { + targets.map(getLocalVarDeclFromLocalVar).toSet + AssumeNode(stmt.pos) + } else { + targets.map(getLocalVarDeclFromLocalVar).toSet + } + case i: Inhale => Set(AssumeNode(i.pos)) + case a: Assume => Set(AssumeNode(a.pos)) + case Label(_, _) => Set() + case Quasihavoc(_, _) => Set(heapVertex) + case Quasihavocall(_, _, _) => Set(heapVertex) + case ExistentialElim(vars, _, _) => vars.toSet + case _ => Set() + } + } + + private def lookupVar(variable: Declaration, graphMap: GraphMap): Set[LocalVarDecl] = { + // Assume Nodes are added when they are first encountered(can be a method call or a assume / inhale statement), so they might not exist when looked up + if(variable.isInstanceOf[AssumeNode]) { + graphMap.getOrElse(variable, Set()) + } else { + assert(graphMap.contains(variable), "Variable " + variable + " not found in graph analysis") + graphMap(variable) + } } /** diff --git a/src/test/resources/reasoning/universal_introduction/ui_assume_methods.vpr b/src/test/resources/reasoning/universal_introduction/ui_assume_methods.vpr new file mode 100644 index 000000000..9d1541af3 --- /dev/null +++ b/src/test/resources/reasoning/universal_introduction/ui_assume_methods.vpr @@ -0,0 +1,152 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +function P(x: Int) : Bool { + x == 0 +} + +function Q(x: Int) : Bool { + x == 0 +} + +method assumeMethodCall1() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + abstractNonTermImpl(10) + if(x > 0) { + //:: ExpectedOutput(consistency.error) + abstractNonTermImpl(10) + } + while(x > 0) { + //:: ExpectedOutput(consistency.error) + abstractNonTermImpl(10) + } + } +} + + +method assumeMethodCall2() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + //:: ExpectedOutput(consistency.error) + abstractNonTermImpl(x) + if(x > 0) { + //:: ExpectedOutput(consistency.error) + abstractNonTermImpl(x) + } + while(x > 0) { + //:: ExpectedOutput(consistency.error) + abstractNonTermImpl(x) + } + } +} + +method assumeMethodCall3() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + //:: ExpectedOutput(consistency.error) + abstractNonTermExpl(x) + if(x > 0) { + //:: ExpectedOutput(consistency.error) + abstractNonTermExpl(x) + } + while(x > 0) { + //:: ExpectedOutput(consistency.error) + abstractNonTermExpl(x) + } + } +} + +method assumeMethodCall4() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + abstractNonTermImplSpec(x) + if(x > 0) { + //:: ExpectedOutput(consistency.error) + abstractNonTermImplSpec(x) + } + while(x > 0) { + //:: ExpectedOutput(consistency.error) + abstractNonTermImplSpec(x) + } + } +} + +method assumeMethodCall5() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + abstractNonTermExplSpec(x) + if(x > 0) { + //:: ExpectedOutput(consistency.error) + abstractNonTermExplSpec(x) + } + while(x > 0) { + //:: ExpectedOutput(consistency.error) + abstractNonTermExplSpec(x) + } + } +} + +method assumeMethodCall6() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + abstractNonTermImplNoAssumesSpec(x) + if(x > 0) { + abstractNonTermImplNoAssumesSpec(x) + } + while(x > 0) { + abstractNonTermImplNoAssumesSpec(x) + } + } +} + +method assumeMethodCall7() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + abstractNonTermExplNoAssumesSpec(x) + if(x > 0) { + abstractNonTermExplNoAssumesSpec(x) + } + while(x > 0) { + abstractNonTermExplNoAssumesSpec(x) + } + } +} + +method assumeMethodCall8() +{ + prove forall x: Int {P(x)} assuming P(x) implies Q(x) { + abstractTerm(x) + if(x > 0) { + abstractTerm(x) + } + while(x > 0) { + abstractTerm(x) + } + } +} + + +method abstractNonTermImpl(x: Int) + +method abstractNonTermImplSpec(x: Int) + influenced assumes by {} + +method abstractNonTermExpl(x: Int) + decreases * + +method abstractNonTermExplSpec(x: Int) + decreases * + influenced assumes by {} + +method abstractNonTermImplNoAssumesSpec(x: Int) + assumesNothing + +method abstractNonTermExplNoAssumesSpec(x: Int) + decreases * + assumesNothing + +method abstractTerm(x: Int) + decreases x + + diff --git a/src/test/resources/reasoning/universal_introduction/ui_methods.vpr b/src/test/resources/reasoning/universal_introduction/ui_methods.vpr index de08a7fcb..ff995f1a3 100644 --- a/src/test/resources/reasoning/universal_introduction/ui_methods.vpr +++ b/src/test/resources/reasoning/universal_introduction/ui_methods.vpr @@ -264,4 +264,26 @@ method test9(x: Ref, y: Int) if (y == 0) { exhale acc(x.f) } -} \ No newline at end of file +} + + +method testRec1(x: Int) + requires acc(x.f) + influenced heap by {heap} +{ + if (y == 0) { + exhale acc(x.f) + } +} + + +method testRec2(x: Int) + requires acc(x.f) + influenced heap by {heap} +{ + testAssumesAbstract1(x) + testRec1(x) +} + +method testAssumesAbstract(x: Int) + assumesUsing {x} \ No newline at end of file From b4a1086fb79de45081376fd2ad5239f9acea380f Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Tue, 21 May 2024 09:19:53 +0200 Subject: [PATCH 25/41] update recursive methods & add noAssumes check --- .../analysis/VarAnalysisGraphMap.scala | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index 59cb3859c..5c6893ad0 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -137,25 +137,28 @@ case class VarAnalysisGraphMap(prog: Program, map = map.removedAll(assumeVars) + (AssumeNode(method.pos) -> assumeVars.flatMap(v => map(v))) } - if(!methodAnalysisMap.contains(method)) { - // Check calculated value against the provided specification if there are any - method.posts.collect({ case f: FlowAnnotation => f }).foreach(f => { - val returnVar = AnalysisUtils.getDeclarationFromFlowVar(f.v, method) - val specifiedInfluences = f.varList.map(v => AnalysisUtils.getLocalVarDeclFromFlowVar(v)).toSet - val calculatedInfluences = lookupVar(returnVar, map) - - if (!calculatedInfluences.subsetOf(specifiedInfluences)) { - reportErrorWithMsg(ConsistencyError(s"Specified influence on return variable $returnVar is missing some potential influences. Specified: $specifiedInfluences Calculated: $calculatedInfluences", f.pos)) - } + // Check calculated value against the provided specification if there are any + method.posts.collect({ case f: FlowAnnotation => f }).foreach(f => { + val returnVar = AnalysisUtils.getDeclarationFromFlowVar(f.v, method) + val specifiedInfluences = f.varList.map(v => AnalysisUtils.getLocalVarDeclFromFlowVar(v)).toSet + val calculatedInfluences = lookupVar(returnVar, map) + + if (!calculatedInfluences.subsetOf(specifiedInfluences)) { + reportErrorWithMsg(ConsistencyError(s"Specified influence on return variable $returnVar is missing some potential influences. Specified: $specifiedInfluences Calculated: $calculatedInfluences", f.pos)) + } - if (calculatedInfluences.intersect(specifiedInfluences).size < calculatedInfluences.size) { - logger.warn(s"Specified influence on return variable $returnVar potentially assumes too many influences. Specified: $specifiedInfluences Calculated: $calculatedInfluences, (${f.pos})") - } - map = map + (returnVar -> specifiedInfluences) - }) + if (calculatedInfluences.intersect(specifiedInfluences).size < calculatedInfluences.size) { + logger.warn(s"Specified influence on return variable $returnVar potentially assumes too many influences. Specified: $specifiedInfluences Calculated: $calculatedInfluences, (${f.pos})") + } - methodAnalysisMap.put(method, map) - } + val noAssumesSpecified = method.pres.concat(method.posts).collect({ case _: NoAssumeAnnotation => true }).nonEmpty + if (noAssumesSpecified && map.contains(AssumeNode(method.pos))) { + reportErrorWithMsg(ConsistencyError(s"Method with assumesNothing specification might perform an assume or inhale", f.pos)) + } + + map = map + (returnVar -> specifiedInfluences) + }) + methodAnalysisMap.put(method, map) } methodAnalysisStarted -= method } From 90a763b1c04c980c054ec492ef860086ff0d1b26 Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Mon, 17 Jun 2024 07:56:06 +0200 Subject: [PATCH 26/41] treat non-terminating loop as assume statement & some cleanup --- .../standard/reasoning/ReasoningPlugin.scala | 6 ++-- .../analysis/VarAnalysisGraphMap.scala | 12 +++++--- .../ui_assume_methods.vpr | 29 ++++++++++++++----- .../universal_introduction/ui_heap.vpr | 2 +- .../universal_introduction/ui_while.vpr | 23 +++++++++++++-- 5 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index 2c361e6c8..a87e3089c 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -48,10 +48,10 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, def assumes[$: P]: P[PAssumes] = P(P(PAssumesKeyword) map (PAssumes(_) _)).pos // note that the parentheses are not redundant def assumesNothingSpec[$: P]: P[PSpecification[PNothingKeyword.type]] = - P(P(PNothingKeyword) map { - case (b) => PSpecification(b, PAssumesNothing()(b.pos))(_) - }).pos + P((P(PNothingKeyword) ~ assumesNothingClause) map (PSpecification.apply _).tupled).pos + // assumes nothing clause is completely artificial and is created out of nowhere at the parser's current position + def assumesNothingClause[$: P]: P[PAssumesNothing] = (Pass(()) map { _ => PAssumesNothing()(_) }).pos def singleVar[$: P]: P[PVar] = P(fp.idnuse map (PVar(_) _)).pos // note that the parentheses are not redundant def varsAndHeap[$: P]: P[Seq[PFlowVar]] = (heap | singleVar).delimited(PSym.Comma).map(_.toSeq) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index 5c6893ad0..b8890aab1 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -111,7 +111,6 @@ case class VarAnalysisGraphMap(prog: Program, if (method.body.isEmpty) { // Case for abstract methods val map = getDefaultMethodInfluences(method) - logger.warn(s"${method.name}: $map") methodAnalysisMap.put(method, map) } else if(methodAnalysisStarted.contains(method)) { // Case for recursive methods @@ -229,7 +228,7 @@ case class VarAnalysisGraphMap(prog: Program, (thenGraph.keySet ++ elseGraph.keySet).map( v => v -> (thenGraph.getOrElse(v, Set()) ++ elseGraph.getOrElse(v, Set()) ++ conditionInfluences.getOrElse(v, Set())) ).toMap - case While(cond, _, body) => + case While(cond, invs, body) => var iterationGraph = computeInfluenceMap(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), graphMap) var edgesEqual: Boolean = false var mergeGraph = iterationGraph @@ -241,7 +240,13 @@ case class VarAnalysisGraphMap(prog: Program, mergeGraph = iterationGraph } } - mergeGraph + + val loopTerminates = invs.collect { case DecreasesTuple(_, _) | DecreasesWildcard(_) => true }.nonEmpty && invs.collect { case DecreasesStar() => true }.isEmpty + if(loopTerminates) { + mergeGraph + } else { + mergeGraph + (AssumeNode(stmt.pos) -> getResolvedVarsFromExp(cond, mergeGraph)) + } case m: MethodCall => val met = prog.findMethod(m.methodName) computeMethodInfluenceMap(graphMap, met, m.args, m.targets, m.pos) @@ -306,7 +311,6 @@ case class VarAnalysisGraphMap(prog: Program, .map(v => retVarMapping(v._1) -> v._2.flatMap(methodArgExpMapping)) + (AnalysisUtils.heapVertex -> (graphMap(AnalysisUtils.heapVertex) ++ methodAnalysisMap(method)(AnalysisUtils.heapVertex).flatMap(methodArgExpMapping))) - logger.warn(s"${method.name} resolved: $resolvedMethodMap") // We set the position to the method call instead of the assume statement, so potential error are more readable. graphMap ++ resolvedMethodMap } diff --git a/src/test/resources/reasoning/universal_introduction/ui_assume_methods.vpr b/src/test/resources/reasoning/universal_introduction/ui_assume_methods.vpr index 9d1541af3..9100fe685 100644 --- a/src/test/resources/reasoning/universal_introduction/ui_assume_methods.vpr +++ b/src/test/resources/reasoning/universal_introduction/ui_assume_methods.vpr @@ -17,7 +17,9 @@ method assumeMethodCall1() //:: ExpectedOutput(consistency.error) abstractNonTermImpl(10) } - while(x > 0) { + while(x > 0) + decreases x + { //:: ExpectedOutput(consistency.error) abstractNonTermImpl(10) } @@ -34,7 +36,9 @@ method assumeMethodCall2() //:: ExpectedOutput(consistency.error) abstractNonTermImpl(x) } - while(x > 0) { + while(x > 0) + decreases x + { //:: ExpectedOutput(consistency.error) abstractNonTermImpl(x) } @@ -50,7 +54,9 @@ method assumeMethodCall3() //:: ExpectedOutput(consistency.error) abstractNonTermExpl(x) } - while(x > 0) { + while(x > 0) + decreases x + { //:: ExpectedOutput(consistency.error) abstractNonTermExpl(x) } @@ -65,7 +71,9 @@ method assumeMethodCall4() //:: ExpectedOutput(consistency.error) abstractNonTermImplSpec(x) } - while(x > 0) { + while(x > 0) + decreases x + { //:: ExpectedOutput(consistency.error) abstractNonTermImplSpec(x) } @@ -80,6 +88,7 @@ method assumeMethodCall5() //:: ExpectedOutput(consistency.error) abstractNonTermExplSpec(x) } + //:: ExpectedOutput(consistency.error) while(x > 0) { //:: ExpectedOutput(consistency.error) abstractNonTermExplSpec(x) @@ -94,7 +103,9 @@ method assumeMethodCall6() if(x > 0) { abstractNonTermImplNoAssumesSpec(x) } - while(x > 0) { + while(x > 0) + decreases x + { abstractNonTermImplNoAssumesSpec(x) } } @@ -107,7 +118,9 @@ method assumeMethodCall7() if(x > 0) { abstractNonTermExplNoAssumesSpec(x) } - while(x > 0) { + while(x > 0) + decreases x + { abstractNonTermExplNoAssumesSpec(x) } } @@ -120,7 +133,9 @@ method assumeMethodCall8() if(x > 0) { abstractTerm(x) } - while(x > 0) { + while(x > 0) + decreases x + { abstractTerm(x) } } diff --git a/src/test/resources/reasoning/universal_introduction/ui_heap.vpr b/src/test/resources/reasoning/universal_introduction/ui_heap.vpr index c47c02fa4..97be70cde 100644 --- a/src/test/resources/reasoning/universal_introduction/ui_heap.vpr +++ b/src/test/resources/reasoning/universal_introduction/ui_heap.vpr @@ -84,7 +84,7 @@ method quasiHavocNOk1(y:Ref) method quasiHavocNOk2(y:Ref) { //:: ExpectedOutput(consistency.error) - prove forall x:Int {P(x)} assuming P(x) implies Q(x) && (y.f > 0 || y.f <= 0) { + prove forall x:Idecreases xnt {P(x)} assuming P(x) implies Q(x) && (y.f > 0 || y.f <= 0) { if(x == 100) { quasihavoc y.f } diff --git a/src/test/resources/reasoning/universal_introduction/ui_while.vpr b/src/test/resources/reasoning/universal_introduction/ui_while.vpr index 45365abd5..2c12f8c60 100644 --- a/src/test/resources/reasoning/universal_introduction/ui_while.vpr +++ b/src/test/resources/reasoning/universal_introduction/ui_while.vpr @@ -61,7 +61,7 @@ method mWhileNOK() var x2: Int := 0 var x1: Int := 0 - while(y<100) { // will be tainted after 3 iterations + while(y<100) { z := x2 x2 := x1 x1 := x @@ -79,7 +79,26 @@ method mWhileNOK2() var x2: Int := 0 var x1: Int := 0 - while(y<100) { // will always be tainted + //:: ExpectedOutput(consistency.error) + while(y<100) { + z := x2 + x2 := x1 + y := y+1 + } + } +} + +method mWhileOK3() +{ + var z: Int := 0 + prove forall x: Int {P(x)} assuming P(x) implies Q(x) && z == 0 { + var y: Int := 0 + var x2: Int := 0 + var x1: Int := 0 + + while(y<100) + decreases y + { z := x2 x2 := x1 y := y+1 From a814df4b82a842704858ae9545cec98b2e237f3c Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Mon, 17 Jun 2024 08:25:18 +0200 Subject: [PATCH 27/41] change position of reported error --- .../standard/reasoning/analysis/VarAnalysisGraphMap.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index b8890aab1..ca772dfb0 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -245,7 +245,7 @@ case class VarAnalysisGraphMap(prog: Program, if(loopTerminates) { mergeGraph } else { - mergeGraph + (AssumeNode(stmt.pos) -> getResolvedVarsFromExp(cond, mergeGraph)) + mergeGraph + (AssumeNode(cond.pos) -> getResolvedVarsFromExp(cond, mergeGraph)) } case m: MethodCall => val met = prog.findMethod(m.methodName) From cf0fea1a61cc1dd13971421b46ca5aa1f16926cd Mon Sep 17 00:00:00 2001 From: jogasser <74091108+jogasser@users.noreply.github.com> Date: Mon, 24 Jun 2024 07:34:00 +0200 Subject: [PATCH 28/41] Update src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala Co-authored-by: Linard Arquint --- .../silver/plugin/standard/reasoning/ReasoningPlugin.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index a87e3089c..0ef5c4531 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -138,7 +138,7 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, /** check that lemma terminates (has a decreases clause) and that it is pure */ checkLemma(input, reportError) - /** create graph with vars that are in scope only outside of the universal introduction code block including the qunatified variables */ + /** create graph with vars that are in scope only outside of the universal introduction code block including the quantified variables */ val analysis: VarAnalysisGraphMap = VarAnalysisGraphMap(input, logger, reportError) /** Run taint analysis for all methods annotated with influences */ From 78960ffb66c766c2720ee6b0911c2eac16248080 Mon Sep 17 00:00:00 2001 From: jogasser <74091108+jogasser@users.noreply.github.com> Date: Mon, 24 Jun 2024 07:34:09 +0200 Subject: [PATCH 29/41] Update src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala Co-authored-by: Linard Arquint --- .../plugin/standard/reasoning/BeforeVerifyHelper.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala index 9967058f8..f2c1eeeac 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala @@ -43,8 +43,9 @@ trait BeforeVerifyHelper { } /** - * get all variables that are assigned to inside the block and take intersection with universal introduction - * variables. If they are contained throw error since these variables should be immutable + * check that all variables (`modified_vars`) that are assigned to inside a universal introduction `u`'s block are + * distinct from the universal introduction `u`'s quantified variables `quantified_vars`. Otherwise, an error is + * reported via `reportError` since these quantified variables should be immutable. * * @param modified_vars: set of variables that were modified in a given statement * @param quantified_vars: set of quantified variables in the universal introduction statement. From e93fd6bb6124dea637e19da42353060e1e73aa10 Mon Sep 17 00:00:00 2001 From: jogasser <74091108+jogasser@users.noreply.github.com> Date: Mon, 24 Jun 2024 07:34:18 +0200 Subject: [PATCH 30/41] Update src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala Co-authored-by: Linard Arquint --- .../silver/plugin/standard/reasoning/BeforeVerifyHelper.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala index f2c1eeeac..fd970545f 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala @@ -61,6 +61,7 @@ trait BeforeVerifyHelper { } } + /** returns true if method `m` is annotated to be a lemma */ private def specifiesLemma(m: Method): Boolean = (m.pres ++ m.posts).exists { case _: Lemma => true case _ => false From 695c91b91e9f088be62f5fa8e3314fcf92ae9ace Mon Sep 17 00:00:00 2001 From: jogasser <74091108+jogasser@users.noreply.github.com> Date: Mon, 24 Jun 2024 07:41:55 +0200 Subject: [PATCH 31/41] Apply suggestions from code review Co-authored-by: Linard Arquint --- .../reasoning/BeforeVerifyHelper.scala | 41 +++--- .../reasoning/ReasoningASTExtension.scala | 27 ++-- .../standard/reasoning/ReasoningErrors.scala | 19 ++- .../reasoning/ReasoningPASTExtension.scala | 1 - .../standard/reasoning/ReasoningPlugin.scala | 135 ++++++++---------- 5 files changed, 96 insertions(+), 127 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala index fd970545f..936a52daf 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala @@ -62,13 +62,13 @@ trait BeforeVerifyHelper { } /** returns true if method `m` is annotated to be a lemma */ - private def specifiesLemma(m: Method): Boolean = (m.pres ++ m.posts).exists { + def specifiesLemma(m: Method): Boolean = (m.pres ++ m.posts).exists { case _: Lemma => true case _ => false } - /** check if isLemma precondition is correct */ - def checkLemma(input: Program, reportError: AbstractError => Unit): Unit = { + /** Checks that all lemmas in `input` satisfy the syntactical restrictions and, otherwise, reports errors by invoking `reportError`. */ + def checkLemmas(input: Program, reportError: AbstractError => Unit): Unit = { input.methods.foreach(method => { val containsLemma = specifiesLemma(method) val (containsDecreases, containsDecreasesStar) = AnalysisUtils.containsDecreasesAnnotations(method) @@ -76,12 +76,12 @@ trait BeforeVerifyHelper { if (containsLemma) { /** report error if there is no decreases clause or specification */ if(!containsDecreases) { - reportError(ConsistencyError(s"method ${method.name} marked lemma might not contain decreases clause", method.pos)) + reportError(ConsistencyError(s"Lemmas must terminate but method ${method.name} marked lemma does not specify any termination measures", method.pos)) } /** report error if the decreases statement might not prove termination */ if (containsDecreasesStar) { - reportError(ConsistencyError("decreases statement might not prove termination", method.pos)) + reportError(ConsistencyError("Lemmas must terminate but method ${method.name} marked lemma specifies only incomplete termination measures", method.pos)) } /** check method body for impure statements */ @@ -90,15 +90,11 @@ trait BeforeVerifyHelper { }) } - /** checks whether the body is pure, reports error if impure operation found */ - def checkBodyPure(stmt: Stmt, method: Method, prog: Program, reportError: AbstractError => Unit): Boolean = { - var pure: Boolean = true + /** checks whether a statement `stmt` is pure, reports error if impure operation found */ + def checkStmtPure(stmt: Stmt, method: Method, prog: Program, reportError: AbstractError => Unit): Boolean = { stmt match { case Seqn(ss, _) => - ss.foreach(s => { - pure = pure && checkBodyPure(s, method, prog, reportError) - }) - pure + ss.forall(s => checkBodyPure(s, method, prog, reportError)) /** case for statements considered impure */ case ie@(Inhale(_) | Exhale(_) | FieldAssign(_, _) | Fold(_) | Unfold(_) | Apply(_) | Package(_, _)) => @@ -106,21 +102,18 @@ trait BeforeVerifyHelper { false case m@MethodCall(methodName, _, _) => val mc = prog.findMethod(methodName) - val containsLemma = specifiesLemma(mc) + val isLemmaCall = specifiesLemma(mc) /** if called method is not a lemma report error */ - if (!containsLemma) { + if (!isLemmaCall) { reportError(ConsistencyError(s"method ${method.name} marked lemma might contain call to method ${m}", m.pos)) - false - } else { - pure } - case _ => - pure + isLemmaCall + case _ => true } } - /** checks that influences by annotations are used correctly. */ + /** checks that all influences by annotations in `input` are used correctly. */ def checkInfluencedBy(input: Program, reportError: AbstractError => Unit): Unit = { input.methods.foreach(method => { val argVars = method.formalArgs.toSet + AnalysisUtils.heapVertex @@ -133,20 +126,20 @@ trait BeforeVerifyHelper { val targetVarDecl = AnalysisUtils.getDeclarationFromFlowVar(target, method) if (!retVars.contains(targetVarDecl)) { - reportError(ConsistencyError(s"Only return variables can be influenced. ${targetVarDecl.name} is not be a return variable.", v.pos)) + reportError(ConsistencyError(s"Only return parameters, the heap or assumes can be influenced. ${targetVarDecl.name} is not be a return parameter.", v.pos)) } if (seenVars.contains(targetVarDecl)) { - reportError(ConsistencyError(s"Only one influenced by expression per return variable can exist. ${targetVarDecl.name} is used several times.", v.pos)) + reportError(ConsistencyError(s"Only one influenced by expression per return parameter can exist. ${targetVarDecl.name} is used several times.", v.pos)) } seenVars.add(targetVarDecl) args.foreach(arg => { val argVarDecl = AnalysisUtils.getLocalVarDeclFromFlowVar(arg) if (!argVars.contains(argVarDecl)) { - reportError(ConsistencyError(s"Only method arguments can influence a return variable. ${argVarDecl.name} is not be a method argument.", v.pos)) + reportError(ConsistencyError(s"Only method input parameters or the heap can influence a return parameter. ${argVarDecl.name} is not be a method input parameter.", v.pos)) } }) - case _ => () + case _ => } }) } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala index 45d259468..a3ab032b0 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala @@ -17,12 +17,12 @@ case object ReasoningInfo extends FailureExpectedInfo case class ExistentialElim(varList: Seq[LocalVarDecl], trigs: Seq[Trigger], exp: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt { override lazy val check: Seq[ConsistencyError] = Consistency.checkPure(exp) ++ - (if (!(exp isSubtype Bool)) Seq(ConsistencyError(s"Body of existential quantifier must be of Bool type, but found ${exp.typ}", exp.pos)) else Seq()) ++ - (if (varList.isEmpty) Seq(ConsistencyError("Quantifier must have at least one quantified variable.", pos)) else Seq()) + (if (!(exp isSubtype Bool)) Seq(ConsistencyError(s"Body of existential elimination must be of Bool type, but found ${exp.typ}", exp.pos)) else Seq()) ++ + (if (varList.isEmpty) Seq(ConsistencyError("Existential elimination must have at least one quantified variable.", pos)) else Seq()) override lazy val prettyPrint: PrettyPrintPrimitives#Cont = { text("obtain") <+> showVars(varList) <+> - text("where") <+> (if (trigs.isEmpty) nil else space <> ssep(trigs map show, space)) <+> + text("where") <+> (if (trigs.isEmpty) nil else ssep(trigs map show, space)) <+> toParenDoc(exp) } @@ -32,10 +32,11 @@ case class ExistentialElim(varList: Seq[LocalVarDecl], trigs: Seq[Trigger], exp: override def declarationsInParentScope: Seq[Declaration] = varList } -case class UniversalIntro(varList: Seq[LocalVarDecl], triggers: Seq[Trigger], exp1: Exp, exp2: Exp, block: Seqn)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt with Scope { +case class UniversalIntro(varList: Seq[LocalVarDecl], triggers: Seq[Trigger], assumingExp: Exp, implyingExp: Exp, block: Seqn)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt with Scope { // See also Expression Line 566 override lazy val check: Seq[ConsistencyError] = - (if (!(exp1 isSubtype Bool)) Seq(ConsistencyError(s"Body of universal quantifier must be of Bool type, but found ${exp1.typ}", exp1.pos)) else Seq()) ++ + (if (!(assumingExp isSubtype Bool)) Seq(ConsistencyError(s"Assume expression of universal introduction must be of Bool type, but found ${assumingExp.typ}", assumingExp.pos)) else Seq()) ++ + (if (!(implyingExp isSubtype Bool)) Seq(ConsistencyError(s"Implies expression of universal introduction must be of Bool type, but found ${implyingExp.typ}", implyingExp.pos)) else Seq()) ++ (if (varList.isEmpty) Seq(ConsistencyError("Quantifier must have at least one quantified variable.", pos)) else Seq()) ++ Consistency.checkAllVarsMentionedInTriggers(varList, triggers) @@ -44,12 +45,12 @@ case class UniversalIntro(varList: Seq[LocalVarDecl], triggers: Seq[Trigger], ex override lazy val prettyPrint: PrettyPrintPrimitives#Cont = { text("prove forall") <+> showVars(varList) <+> text("assuming") <+> - toParenDoc(exp1) <+> - text("implies") <+> toParenDoc(exp2) <+> + toParenDoc(assumingExp) <+> + text("implies") <+> toParenDoc(implyingExp) <+> showBlock(block) } - override val extensionSubnodes: Seq[Node] = varList ++ triggers ++ Seq(exp1, exp2, block) + override val extensionSubnodes: Seq[Node] = varList ++ triggers ++ Seq(assumingExp, implyingExp, block) } sealed trait FlowVar extends ExtensionExp { @@ -152,13 +153,9 @@ case class OldCall(methodName: String, args: Seq[Exp], rets: Seq[LocalVar], oldL override val scopedDecls: Seq[Declaration] = Seq() override lazy val check: Seq[ConsistencyError] = { - var s = Seq.empty[ConsistencyError] - if (!Consistency.noResult(this)) - s :+= ConsistencyError("Result variables are only allowed in postconditions of functions.", pos) - if (!Consistency.noDuplicates(rets)) - s :+= ConsistencyError("Targets are not allowed to have duplicates", rets.head.pos) - s ++= args.flatMap(Consistency.checkPure) - s + (if (Consistency.noResult(this)) Seq.empty else ConsistencyError("Result variables are only allowed in postconditions of functions.", pos)) ++ + (if (Consistency.noDuplicates(rets)) Seq.empty else ConsistencyError("Targets are not allowed to have duplicates", rets.head.pos)) ++ + args.flatMap(Consistency.checkPure) } override lazy val prettyPrint: PrettyPrintPrimitives#Cont = { diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala index 04088775e..6eea6012e 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningErrors.scala @@ -10,8 +10,8 @@ import viper.silver.verifier._ import viper.silver.verifier.reasons.ErrorNode case class ExistentialElimFailed(override val offendingNode: ErrorNode, override val reason: ErrorReason, override val cached: Boolean = false) extends ExtensionAbstractVerificationError { - override val id = "existential elimination.failed" - override val text = " no witness could be found." + override val id = "existential.elimination.failed" + override val text = "Existentially quantified formula might not hold." override def withNode(offendingNode: errors.ErrorNode = this.offendingNode): ExistentialElimFailed = ExistentialElimFailed(this.offendingNode, this.reason, this.cached) @@ -20,8 +20,8 @@ case class ExistentialElimFailed(override val offendingNode: ErrorNode, override } case class UniversalIntroFailed(override val offendingNode: ErrorNode, override val reason: ErrorReason, override val cached: Boolean = false) extends ExtensionAbstractVerificationError { - override val id = "universal introduction.failed" - override val text = " not true for all vars." + override val id = "universal.introduction.failed" + override val text = "Specified property might not hold." override def withNode(offendingNode: errors.ErrorNode = this.offendingNode): UniversalIntroFailed = UniversalIntroFailed(this.offendingNode, this.reason, this.cached) @@ -29,12 +29,11 @@ case class UniversalIntroFailed(override val offendingNode: ErrorNode, override override def withReason(r: ErrorReason): UniversalIntroFailed = UniversalIntroFailed(offendingNode, r, cached) } -case class FlowAnalysisFailed(override val offendingNode: ErrorNode, override val reason: ErrorReason, override val cached: Boolean = false) extends ExtensionAbstractVerificationError { - override val id = "flow analysis.failed" - override val text = " ." +case class PreconditionInLemmaCallFalse(offendingNode: OldCall, reason: ErrorReason, override val cached: Boolean = false) extends ExtensionAbstractVerificationError { + val id = "lemma.call.precondition" + val text = s"The precondition of lemma ${offendingNode.methodName} might not hold." - override def withNode(offendingNode: errors.ErrorNode = this.offendingNode): FlowAnalysisFailed = - FlowAnalysisFailed(this.offendingNode, this.reason, this.cached) + def withNode(offendingNode: errors.ErrorNode = this.offendingNode): PreconditionInLemmaCallFalse = PreconditionInLemmaCallFalse(offendingNode.asInstanceOf[OldCall], this.reason, this.cached) - override def withReason(r: ErrorReason): FlowAnalysisFailed = FlowAnalysisFailed(offendingNode, r, cached) + def withReason(r: ErrorReason): PreconditionInLemmaCallFalse = PreconditionInLemmaCallFalse(offendingNode, r, cached) } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala index 97bccabb3..686325fbf 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -71,7 +71,6 @@ case class PFlowAnnotation(v: PFlowVar, byKw: PReserved[PByKeyword.type], groupe override def forceSubstitution(ts: PTypeSubstitution): Unit = {} - //from here new override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = { varList.foreach { diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index 0ef5c4531..8abd6dc1f 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -137,7 +137,7 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, val usedNames: mutable.Set[String] = collection.mutable.Set(input.transitiveScopedDecls.map(_.name): _*) /** check that lemma terminates (has a decreases clause) and that it is pure */ - checkLemma(input, reportError) + checkLemmas(input, reportError) /** create graph with vars that are in scope only outside of the universal introduction code block including the quantified variables */ val analysis: VarAnalysisGraphMap = VarAnalysisGraphMap(input, logger, reportError) @@ -151,104 +151,82 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, /** remove the influenced by postconditions. * remove isLemma */ case m: Method => - var postconds: Seq[Exp] = Seq() - m.posts.foreach { - case _: FlowAnnotation => - postconds = postconds - case _: Lemma => - postconds = postconds - case s@_ => - postconds = postconds ++ Seq(s) + val flowAnnotationAndLemmaFilter: Exp => Boolean = { + case _: FlowAnnotation | _: Lemma => false + case _ => true } - var preconds: Seq[Exp] = Seq() - m.pres.foreach { - case _: Lemma => - preconds = preconds - case s@_ => - preconds = preconds ++ Seq(s) + val postconds = m.posts.filter(flowAnnotationAndLemmaFilter) + val preconds = m.pres.filter(flowAnnotationAndLemmaFilter) + if (postconds != m.posts || preconds != m.pres) { + m.copy(pres = preconds, posts = postconds)(m.pos, m.info, m.errT) + } else { + m } - val newMethod = - if (postconds != m.posts || preconds != m.pres) { - m.copy(pres = preconds, posts = postconds)(m.pos, m.info, m.errT) - } else { - m - } - - newMethod case o@OldCall(methodName, args, rets, lbl) => /** check whether called method is a lemma */ val currmethod = input.findMethod(methodName) - val isLemma = (currmethod.pres ++ currmethod.posts).exists { - case _: Lemma => true - case _ => false - } + val isLemma = specifiesLemma(currmethod) if (!isLemma) { reportError(ConsistencyError(s"method ${currmethod.name} called in old context must be lemma", o.pos)) } - var new_pres: Seq[Exp] = Seq() - var new_posts: Seq[Exp] = Seq() - var new_v_map: Seq[(LocalVarDecl, Exp)] = - (args zip currmethod.formalArgs).map(zipped => { - val formal_a: LocalVarDecl = zipped._2 - val arg_exp: Exp = zipped._1 + val new_v_map: Seq[(LocalVarDecl, Exp)] = ((currmethod.formalArgs ++ currmethod.formalReturns) zip (args ++ rets)).map(zipped => { + val formal_a: LocalVarDecl = zipped._1 + val arg_exp: Exp = zipped._2 formal_a -> arg_exp }) - new_v_map ++= - (rets zip currmethod.formalReturns).map(zipped => { - val formal_r: LocalVarDecl = zipped._2 - val r: LocalVar = zipped._1 - formal_r -> r - }) - /** replace all variables in precondition with fresh variables */ - currmethod.pres.foreach { - case Lemma() => () - case p => - new_pres ++= Seq(applySubstitutionWithExp(new_v_map, p)) + + /** replace all input parameters in preconditions with the corresponding argument */ + val new_pres = currmethod.pres.flatMap { + case Lemma() => Seq.empty + case p => Seq(applySubstitutionWithExp(new_v_map, p)) } - /** replace all variables in postcondition with fresh variables */ - currmethod.posts.foreach { - case Lemma() => () - case p => - new_posts ++= Seq(applySubstitutionWithExp(new_v_map, p)) + /** replace all input & output parameters in postconditions with the corresponding argument / result */ + val new_posts = currmethod.posts.flatMap { + case Lemma() => Seq.empty + case p => Seq(applySubstitutionWithExp(new_v_map, p)) } /** create new variable declarations to havoc the lhs of the oldCall */ - var new_v_decls: Seq[LocalVarDecl] = Seq() - var rTov: Map[LocalVar,LocalVarDecl] = Map() - for (r <- rets) { + val rToV = rets.map(r => { val new_v = LocalVarDecl(uniqueName(".v", usedNames),r.typ)(r.pos) - new_v_decls = new_v_decls ++ Seq(new_v) - rTov += (r -> new_v) - } + r -> new_v + }).toMap + + val errTransformer = ErrTrafo({ + case AssertFailed(_, r, c) => PreconditionInLemmaCallFalse(o, r, c) + case d => d + }) Seqn( + // assert precondition(s) new_pres.map(p => - Assert(LabelledOld(p, lbl)(p.pos))(o.pos) - ) - ++ - - rets.map(r => { - LocalVarAssign(r,rTov(r).localVar)(o.pos) - }) - ++ - - - new_posts.map(p => + Assert(LabelledOld(p, lbl)(p.pos))(o.pos, errT = errTransformer) + ) ++ + // havoc return args by assigning an unconstrained value + rets.map(r => { + LocalVarAssign(r, rToV(r).localVar)(o.pos) + }) ++ + // inhale postcondition(s) + new_posts.map(p => Inhale(LabelledOld(p, lbl)(p.pos))(o.pos) ), - new_v_decls + rToV.values.toSeq )(o.pos) case e@ExistentialElim(v, trigs, exp) => val (new_v_map, new_exp) = substituteWithFreshVars(v, exp, usedNames) val new_trigs = trigs.map(t => Trigger(t.exps.map(e1 => applySubstitution(new_v_map, e1)))(t.pos)) + val errTransformer = ErrTrafo({ + case AssertFailed(_, r, c) => ExistentialElimFailed(e, r, c) + case d => d + }) Seqn( Seq( - Assert(Exists(new_v_map.map(_._2), new_trigs, new_exp)(e.pos, ReasoningInfo))(e.pos) + Assert(Exists(new_v_map.map(_._2), new_trigs, new_exp)(e.pos, errT = errTransformer))(e.pos) ) ++ v.map(variable => LocalVarDeclStmt(variable)(variable.pos)) //list of variables @@ -286,26 +264,29 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, val quantifiedVars = newVarMap.map(vars => vars._2) val newTrigs = trigs.map(t => Trigger(t.exps.map(e1 => applySubstitution(newVarMap, e1)))(t.pos)) val lbl = uniqueName("l", usedNames) + val errTransformer = ErrTrafo({ + case AssertFailed(_, r, c) => UniversalIntroFailed(u, r, c) + case d => d + }) Seqn( Seq( - Label(lbl, Seq())(u.pos), + Label(lbl, Seq.empty)(u.pos), + // conditionally inhale assume expression If(boolvar.localVar, Seqn( - Seq( - Inhale(exp1)(exp1.pos) - ), - Seq())(exp1.pos), - Seqn(Seq(), Seq())(exp1.pos) - + Seq(Inhale(exp1)(exp1.pos)), + Seq.empty)(exp1.pos), + Seqn(Seq.empty, Seq.empty)(exp1.pos) )(exp1.pos), + // execute block blk, - Assert(Implies(boolvar.localVar, exp2)(exp2.pos))(exp2.pos), + // conditionally assert imply expression + Assert(Implies(boolvar.localVar, exp2)(exp2.pos))(exp2.pos, errT = errTransformer), Inhale(Forall(quantifiedVars, newTrigs, Implies(LabelledOld(newExp1, lbl)(exp2.pos), newExp2)(exp2.pos))(exp2.pos))(exp2.pos) ), Seq(boolvar) ++ v )(exp1.pos) - }, Traverse.TopDown).execute[Program](input) newAst From 9bb7f88c34fed1b80b8d8b357938b0511fbad877 Mon Sep 17 00:00:00 2001 From: jogasser <74091108+jogasser@users.noreply.github.com> Date: Mon, 24 Jun 2024 07:44:16 +0200 Subject: [PATCH 32/41] Apply suggestions from code review Co-authored-by: Linard Arquint --- .../standard/reasoning/analysis/VarAnalysisGraphMap.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index ca772dfb0..785f5b04f 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -44,9 +44,9 @@ object AnalysisUtils { LocalVarDecl(l.name, l.typ)() } - def containsDecreasesAnnotations(m: Method): (Boolean, Boolean) = { - val presContain = m.pres.collect({ case DecreasesTuple(_, _) | DecreasesWildcard(_) | DecreasesStar() => true }).nonEmpty - val postsContain = m.posts.collect({ case DecreasesTuple(_, _) | DecreasesWildcard(_) | DecreasesStar() => true }).nonEmpty + def specifiesTermination(m: Method): (Boolean, Boolean) = { + val presContain = m.pres.collect({ case DecreasesTuple(_, _) | DecreasesWildcard(_) => true }).nonEmpty + val postsContain = m.posts.collect({ case DecreasesTuple(_, _) | DecreasesWildcard(_) => true }).nonEmpty val presContainStar = m.pres.collect({ case DecreasesStar() => true }).nonEmpty val postsContainStar = m.posts.collect({ case DecreasesStar() => true }).nonEmpty @@ -63,7 +63,7 @@ object AnalysisUtils { case _ => false } - val containsTerminationMeasure = presContain | postsContain | presContainStar | postsContainStar | infoContains + val containsTerminationMeasure = presContain | postsContain | presContainStar | postsContainStar | infoContains | infoContainsStar val mightNotTerminate = !containsTerminationMeasure | presContainStar | postsContainStar | infoContainsStar (containsTerminationMeasure, mightNotTerminate) From a0617cee45749699556de655a0cd3dc3d9c16261 Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Mon, 24 Jun 2024 08:03:32 +0200 Subject: [PATCH 33/41] address mr comments --- .../standard/reasoning/BeforeVerifyHelper.scala | 10 +++++----- .../reasoning/ReasoningASTExtension.scala | 14 ++++++++------ .../reasoning/ReasoningPASTExtension.scala | 13 +++++++++---- .../standard/reasoning/ReasoningPlugin.scala | 13 +++++-------- .../analysis/VarAnalysisGraphMap.scala | 17 +++++++++++------ 5 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala index 936a52daf..d4b9aec92 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala @@ -71,7 +71,7 @@ trait BeforeVerifyHelper { def checkLemmas(input: Program, reportError: AbstractError => Unit): Unit = { input.methods.foreach(method => { val containsLemma = specifiesLemma(method) - val (containsDecreases, containsDecreasesStar) = AnalysisUtils.containsDecreasesAnnotations(method) + val (containsDecreases, containsDecreasesStar) = AnalysisUtils.specifiesTermination(method) if (containsLemma) { /** report error if there is no decreases clause or specification */ @@ -85,7 +85,7 @@ trait BeforeVerifyHelper { } /** check method body for impure statements */ - checkBodyPure(method.body.getOrElse(Seqn(Seq(), Seq())()), method, input, reportError) + checkStmtPure(method.body.getOrElse(Seqn(Seq(), Seq())()), method, input, reportError) } }) } @@ -94,11 +94,11 @@ trait BeforeVerifyHelper { def checkStmtPure(stmt: Stmt, method: Method, prog: Program, reportError: AbstractError => Unit): Boolean = { stmt match { case Seqn(ss, _) => - ss.forall(s => checkBodyPure(s, method, prog, reportError)) + ss.forall(s => checkStmtPure(s, method, prog, reportError)) /** case for statements considered impure */ case ie@(Inhale(_) | Exhale(_) | FieldAssign(_, _) | Fold(_) | Unfold(_) | Apply(_) | Package(_, _)) => - reportError(ConsistencyError(s"method ${method.name} marked lemma might contain impure statement ${ie}", ie.pos)) + reportError(ConsistencyError(s"method ${method.name} marked lemma might contain impure statement $ie", ie.pos)) false case m@MethodCall(methodName, _, _) => val mc = prog.findMethod(methodName) @@ -106,7 +106,7 @@ trait BeforeVerifyHelper { /** if called method is not a lemma report error */ if (!isLemmaCall) { - reportError(ConsistencyError(s"method ${method.name} marked lemma might contain call to method ${m}", m.pos)) + reportError(ConsistencyError(s"method ${method.name} marked lemma might contain call to method $m", m.pos)) } isLemmaCall case _ => true diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala index a3ab032b0..5b99a8dbf 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala @@ -62,19 +62,21 @@ sealed trait FlowVar extends ExtensionExp { } } +trait FlowVarOrHeap extends FlowVar + case class Assumes()(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowVar { override val extensionSubnodes: Seq[Node] = Seq.empty override def typ: Type = InternalType override def prettyPrint: PrettyPrintPrimitives#Cont = PAssumesKeyword.keyword } -case class Heap()(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowVar { +case class Heap()(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowVarOrHeap { override val extensionSubnodes: Seq[Node] = Seq.empty override def typ: Type = InternalType override def prettyPrint: PrettyPrintPrimitives#Cont = PHeapKeyword.keyword } -case class Var(decl: LocalVar)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowVar { +case class Var(decl: LocalVar)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowVarOrHeap { override val extensionSubnodes: Seq[Node] = Seq(decl) override def typ: Type = decl.typ override def prettyPrint: PrettyPrintPrimitives#Cont = show(decl) @@ -99,7 +101,7 @@ case class NoAssumeAnnotation()(val pos: Position = NoPosition, val info: Info = } } -case class FlowAnnotation(v: FlowVar, varList: Seq[FlowVar])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionExp with Node with Scope { +case class FlowAnnotation(v: FlowVar, varList: Seq[FlowVarOrHeap])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionExp with Node with Scope { override def extensionIsPure: Boolean = true override val scopedDecls: Seq[Declaration] = Seq() @@ -117,7 +119,7 @@ case class FlowAnnotation(v: FlowVar, varList: Seq[FlowVar])(val pos: Position = * Sample implementation would be text("old") <> parens(show(e)) for pretty-printing an old-expression. */ override def prettyPrint: PrettyPrintPrimitives#Cont = { text("influenced") <+> (v match { - case value: Var => (show(value.decl)) + case value: Var => show(value.decl) case _ => text("heap") }) <+> text("by") <+> @@ -153,8 +155,8 @@ case class OldCall(methodName: String, args: Seq[Exp], rets: Seq[LocalVar], oldL override val scopedDecls: Seq[Declaration] = Seq() override lazy val check: Seq[ConsistencyError] = { - (if (Consistency.noResult(this)) Seq.empty else ConsistencyError("Result variables are only allowed in postconditions of functions.", pos)) ++ - (if (Consistency.noDuplicates(rets)) Seq.empty else ConsistencyError("Targets are not allowed to have duplicates", rets.head.pos)) ++ + (if (Consistency.noResult(this)) Seq.empty else Seq(ConsistencyError("Result variables are only allowed in postconditions of functions.", pos))) ++ + (if (Consistency.noDuplicates(rets)) Seq.empty else Seq(ConsistencyError("Targets are not allowed to have duplicates", rets.head.pos))) ++ args.flatMap(Consistency.checkPure) } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala index 686325fbf..04676c199 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -64,8 +64,8 @@ case class PUniversalIntro(proveKw: PReserved[PProveKeyword.type], forallKw: PKw } } -case class PFlowAnnotation(v: PFlowVar, byKw: PReserved[PByKeyword.type], groupedVarList: PGrouped[PSym.Brace, Seq[PFlowVar]])(val pos: (Position,Position)) extends PExtender with PExp { - lazy val varList: Seq[PFlowVar] = groupedVarList.inner +case class PFlowAnnotation(v: PFlowVar, byKw: PReserved[PByKeyword.type], groupedVarList: PGrouped[PSym.Brace, Seq[PFlowVarOrHeap]])(val pos: (Position,Position)) extends PExtender with PExp { + lazy val varList: Seq[PFlowVarOrHeap] = groupedVarList.inner override def typeSubstitutions: Seq[PTypeSubstitution] = Seq(PTypeSubstitution.id) @@ -104,7 +104,12 @@ sealed trait PFlowVar extends PExtender with PExp { override def forceSubstitution(ts: PTypeSubstitution): Unit = {} } -case class PHeap(heap: PReserved[PHeapKeyword.type])(val pos: (Position,Position)) extends PFlowVar { +trait PFlowVarOrHeap extends PFlowVar { + + override def translate(t: Translator): FlowVarOrHeap +} + +case class PHeap(heap: PReserved[PHeapKeyword.type])(val pos: (Position,Position)) extends PFlowVarOrHeap { override def translate(t: Translator): Heap = { Heap()(t.liftPos(this)) } @@ -135,7 +140,7 @@ case class PAssumesNothing()(val pos: (Position,Position)) extends PExp with PEx override def forceSubstitution(ts: PTypeSubstitution): Unit = {} } -case class PVar(decl: PIdnUseExp)(val pos: (Position,Position)) extends PFlowVar { +case class PVar(decl: PIdnUseExp)(val pos: (Position,Position)) extends PFlowVarOrHeap { override def translate(t: Translator): Var = { // due to the implementation of `t.exp`, a LocalVar should be returned Var(t.exp(decl).asInstanceOf[LocalVar])(t.liftPos(this)) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index 8abd6dc1f..8cb02cd49 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -15,6 +15,7 @@ import viper.silver.parser._ import viper.silver.plugin.standard.reasoning.analysis.VarAnalysisGraphMap import viper.silver.plugin.{ParserPluginTemplate, SilverPlugin} import viper.silver.verifier._ +import viper.silver.verifier.errors.AssertFailed import scala.annotation.unused import scala.collection.mutable @@ -53,7 +54,7 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, // assumes nothing clause is completely artificial and is created out of nowhere at the parser's current position def assumesNothingClause[$: P]: P[PAssumesNothing] = (Pass(()) map { _ => PAssumesNothing()(_) }).pos def singleVar[$: P]: P[PVar] = P(fp.idnuse map (PVar(_) _)).pos // note that the parentheses are not redundant - def varsAndHeap[$: P]: P[Seq[PFlowVar]] = (heap | singleVar).delimited(PSym.Comma).map(_.toSeq) + def varsAndHeap[$: P]: P[Seq[PFlowVarOrHeap]] = (heap | singleVar).delimited(PSym.Comma).map(_.toSeq) def influencedBy[$: P]: P[PFlowAnnotation] = P(((heap | assumes | singleVar) ~ P(PByKeyword) ~/ varsAndHeap.braces) map { case (v, byKw, groupedVarList) => PFlowAnnotation(v, byKw, groupedVarList)(_) @@ -92,7 +93,7 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, ParserExtension.addNewKeywords(Set(PObtainKeyword, PWhereKeyword, PProveKeyword, PAssumingKeyword, PImpliesKeyword)) /** keywords for flow annotation and therefore modular flow analysis */ - ParserExtension.addNewKeywords(Set(PInfluencedKeyword, PByKeyword, PHeapKeyword)) + ParserExtension.addNewKeywords(Set(PInfluencedKeyword, PByKeyword, PHeapKeyword, PNothingKeyword, PAssumesKeyword)) /** keyword to declare a lemma and to call the lemma in an old context*/ ParserExtension.addNewKeywords(Set(PIsLemmaKeyword)) @@ -142,11 +143,9 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, val analysis: VarAnalysisGraphMap = VarAnalysisGraphMap(input, logger, reportError) /** Run taint analysis for all methods annotated with influences */ - input.methods.filter(m => m.posts.collect({ case i: FlowAnnotation => i }).nonEmpty).foreach( - method => analysis.executeTaintedGraphMethodAnalysis(method) - ) + analysis.checkUserProvidedInfluencesSpec() - val newAst: Program = ViperStrategy.Slim({ + ViperStrategy.Slim({ /** remove the influenced by postconditions. * remove isLemma */ @@ -288,7 +287,5 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, Seq(boolvar) ++ v )(exp1.pos) }, Traverse.TopDown).execute[Program](input) - - newAst } } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index 785f5b04f..3b8533ca9 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -9,16 +9,15 @@ package viper.silver.plugin.standard.reasoning.analysis import viper.silver.ast.utility.Expressions import viper.silver.ast.{AccessPredicate, Apply, Assert, Assume, BinExp, CurrentPerm, Declaration, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, Fold, ForPerm, FuncApp, Goto, If, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, Method, MethodCall, Package, Position, Program, Quasihavoc, Quasihavocall, Ref, Scope, Seqn, Stmt, UnExp, Unfold, While} import viper.silver.plugin.standard.reasoning.analysis.AnalysisUtils.{AssumeNode, getLocalVarDeclFromLocalVar, heapVertex} -import viper.silver.plugin.standard.reasoning.{Assumes, ExistentialElim, FlowAnnotation, FlowVar, Heap, NoAssumeAnnotation, OldCall, UniversalIntro, Var} +import viper.silver.plugin.standard.reasoning.{Assumes, ExistentialElim, FlowAnnotation, FlowVar, FlowVarOrHeap, Heap, NoAssumeAnnotation, OldCall, UniversalIntro, Var} import viper.silver.plugin.standard.termination.{DecreasesSpecification, DecreasesStar, DecreasesTuple, DecreasesWildcard} import viper.silver.verifier.{AbstractError, ConsistencyError} import scala.collection.mutable object AnalysisUtils { - - // TODO Add NoAssume Specification (default for all cases except for non-terminating abstract methods (e.g AssumeFree). - // Also check method Body for assumes (or inhales in case this specification is present + // TODO add traits for kind of assume sources(e.g assume/inhale stmt, method call, while loop) + // Position is used for identifying a source for an assume(assume stmt, method call, while loop etc.) and for printing error messages case class AssumeNode(pos: Position) extends Declaration { override def name: String = ".assume" } @@ -33,7 +32,7 @@ object AnalysisUtils { } } - def getLocalVarDeclFromFlowVar(f: FlowVar): LocalVarDecl = { + def getLocalVarDeclFromFlowVar(f: FlowVarOrHeap): LocalVarDecl = { f match { case v: Var => LocalVarDecl(v.decl.name, v.decl.typ)(v.decl.pos) case _: Heap => heapVertex @@ -82,6 +81,12 @@ case class VarAnalysisGraphMap(prog: Program, private val methodAnalysisStarted: mutable.ListBuffer[Method] = mutable.ListBuffer() + def checkUserProvidedInfluencesSpec(): Unit = { + prog.methods.filter(m => m.posts.collect({ case i: FlowAnnotation => i }).nonEmpty).foreach( + method => executeTaintedGraphMethodAnalysis(method) + ) + } + /** execute the information flow analysis with graphs. * When executed on the universal introduction statement the tainted variables are simply the quantified variables */ def executeTaintedGraphAnalysis(allVars: Set[LocalVarDecl], tainted: Set[LocalVarDecl], blk: Seqn, volatileVars: Set[LocalVarDecl], u: UniversalIntro): Unit = { @@ -184,7 +189,7 @@ case class VarAnalysisGraphMap(prog: Program, val otherInfluences = (retSet -- annotationInfluences.keySet).map(retVar => { retVar -> allMethodArgsSet }) - val (containsDecreases, containsDecreasesStar) = AnalysisUtils.containsDecreasesAnnotations(method) + val (containsDecreases, containsDecreasesStar) = AnalysisUtils.specifiesTermination(method) val terminates = containsDecreases && !containsDecreasesStar val noAssumes = method.pres.concat(method.posts).collect({case _: NoAssumeAnnotation => true}).nonEmpty From a452e1d57b2168c8f713729b721f9a1024113e4b Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Mon, 24 Jun 2024 08:20:59 +0200 Subject: [PATCH 34/41] split AssumeNode into multiple AssumeNodes for better error reporting --- .../reasoning/BeforeVerifyHelper.scala | 2 +- .../analysis/VarAnalysisGraphMap.scala | 39 +++++++++++-------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala index d4b9aec92..20469ea34 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala @@ -117,7 +117,7 @@ trait BeforeVerifyHelper { def checkInfluencedBy(input: Program, reportError: AbstractError => Unit): Unit = { input.methods.foreach(method => { val argVars = method.formalArgs.toSet + AnalysisUtils.heapVertex - val retVars = method.formalReturns.toSet.asInstanceOf[Set[Declaration]] + AnalysisUtils.heapVertex + AnalysisUtils.AssumeNode(method.pos) + val retVars = method.formalReturns.toSet.asInstanceOf[Set[Declaration]] + AnalysisUtils.heapVertex + AnalysisUtils.AssumeMethodNode(method.pos) val seenVars: mutable.Set[Declaration] = mutable.Set() /** iterate through method postconditions to find flow annotations */ diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index 3b8533ca9..741701339 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -8,7 +8,7 @@ package viper.silver.plugin.standard.reasoning.analysis import viper.silver.ast.utility.Expressions import viper.silver.ast.{AccessPredicate, Apply, Assert, Assume, BinExp, CurrentPerm, Declaration, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, Fold, ForPerm, FuncApp, Goto, If, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, Method, MethodCall, Package, Position, Program, Quasihavoc, Quasihavocall, Ref, Scope, Seqn, Stmt, UnExp, Unfold, While} -import viper.silver.plugin.standard.reasoning.analysis.AnalysisUtils.{AssumeNode, getLocalVarDeclFromLocalVar, heapVertex} +import viper.silver.plugin.standard.reasoning.analysis.AnalysisUtils.{AssumeMethodNode, AssumeNode, AssumeStmtNode, AssumeWhileNode, getLocalVarDeclFromLocalVar, heapVertex} import viper.silver.plugin.standard.reasoning.{Assumes, ExistentialElim, FlowAnnotation, FlowVar, FlowVarOrHeap, Heap, NoAssumeAnnotation, OldCall, UniversalIntro, Var} import viper.silver.plugin.standard.termination.{DecreasesSpecification, DecreasesStar, DecreasesTuple, DecreasesWildcard} import viper.silver.verifier.{AbstractError, ConsistencyError} @@ -18,9 +18,12 @@ import scala.collection.mutable object AnalysisUtils { // TODO add traits for kind of assume sources(e.g assume/inhale stmt, method call, while loop) // Position is used for identifying a source for an assume(assume stmt, method call, while loop etc.) and for printing error messages - case class AssumeNode(pos: Position) extends Declaration { + trait AssumeNode extends Declaration { override def name: String = ".assume" } + case class AssumeMethodNode(pos: Position) extends AssumeNode + case class AssumeWhileNode(pos: Position) extends AssumeNode + case class AssumeStmtNode(pos: Position) extends AssumeNode val heapVertex: LocalVarDecl = LocalVarDecl(".heap", Ref)() @@ -28,7 +31,7 @@ object AnalysisUtils { f match { case v: Var => LocalVarDecl(v.decl.name, v.decl.typ)(v.decl.pos) case _: Heap => heapVertex - case _: Assumes => AssumeNode(m.pos) + case _: Assumes => AssumeMethodNode(m.pos) } } @@ -100,9 +103,11 @@ case class VarAnalysisGraphMap(prog: Program, } val assumeProblems = graph.keySet.filter(v => v.isInstanceOf[AssumeNode] && graph(v).intersect(tainted).nonEmpty) - assumeProblems.foreach(v => - reportErrorWithMsg(ConsistencyError(s"Universal introduction variable might influence assume statement", v.pos)) - ) + assumeProblems.foreach { + case AssumeMethodNode(p) => reportErrorWithMsg(ConsistencyError(s"Universal introduction variable might influence the method's termination or assume statements in the method's body", p)) + case AssumeStmtNode(p) => reportErrorWithMsg(ConsistencyError(s"Universal introduction variable might influence assume statement", p)) + case AssumeWhileNode(p) => reportErrorWithMsg(ConsistencyError(s"Universal introduction variable might influence loop termination", p)) + } } /** @@ -138,7 +143,7 @@ case class VarAnalysisGraphMap(prog: Program, // Remove all assume nodes and save them as a single assume node for the whole method if(assumeVars.nonEmpty) { - map = map.removedAll(assumeVars) + (AssumeNode(method.pos) -> assumeVars.flatMap(v => map(v))) + map = map.removedAll(assumeVars) + (AssumeMethodNode(method.pos) -> assumeVars.flatMap(v => map(v))) } // Check calculated value against the provided specification if there are any @@ -156,7 +161,7 @@ case class VarAnalysisGraphMap(prog: Program, } val noAssumesSpecified = method.pres.concat(method.posts).collect({ case _: NoAssumeAnnotation => true }).nonEmpty - if (noAssumesSpecified && map.contains(AssumeNode(method.pos))) { + if (noAssumesSpecified && map.contains(AssumeMethodNode(method.pos))) { reportErrorWithMsg(ConsistencyError(s"Method with assumesNothing specification might perform an assume or inhale", f.pos)) } @@ -193,10 +198,10 @@ case class VarAnalysisGraphMap(prog: Program, val terminates = containsDecreases && !containsDecreasesStar val noAssumes = method.pres.concat(method.posts).collect({case _: NoAssumeAnnotation => true}).nonEmpty - if(annotationInfluences.contains(AssumeNode(method.pos)) || terminates || noAssumes) { + if(annotationInfluences.contains(AssumeMethodNode(method.pos)) || terminates || noAssumes) { annotationInfluences ++ otherInfluences } else { - annotationInfluences ++ otherInfluences + (AssumeNode(method.pos) -> allMethodArgsSet) + annotationInfluences ++ otherInfluences + (AssumeMethodNode(method.pos) -> allMethodArgsSet) } } @@ -250,7 +255,7 @@ case class VarAnalysisGraphMap(prog: Program, if(loopTerminates) { mergeGraph } else { - mergeGraph + (AssumeNode(cond.pos) -> getResolvedVarsFromExp(cond, mergeGraph)) + mergeGraph + (AssumeWhileNode(cond.pos) -> getResolvedVarsFromExp(cond, mergeGraph)) } case m: MethodCall => val met = prog.findMethod(m.methodName) @@ -284,8 +289,8 @@ case class VarAnalysisGraphMap(prog: Program, case Label(_, _) => graphMap // Assume analysis case a: Inhale => - val assumeVars = (lookupVar(AssumeNode(a.pos), graphMap) ++ getResolvedVarsFromExp(a.exp, graphMap)).flatMap(v => lookupVar(v, graphMap) + v) - graphMap + (AssumeNode(a.pos) -> assumeVars) + val assumeVars = (lookupVar(AssumeStmtNode(a.pos), graphMap) ++ getResolvedVarsFromExp(a.exp, graphMap)).flatMap(v => lookupVar(v, graphMap) + v) + graphMap + (AssumeStmtNode(a.pos) -> assumeVars) case ExistentialElim(vars, _, exp) => graphMap ++ vars.map(v => v -> getResolvedVarsFromExp(exp, graphMap)) // Non handled cases case a: Assume => @@ -309,7 +314,7 @@ case class VarAnalysisGraphMap(prog: Program, ).toMap + (AnalysisUtils.heapVertex -> Set(AnalysisUtils.heapVertex)) val retVarMapping = (callTargets.map(l => AnalysisUtils.getLocalVarDeclFromLocalVar(l)) zip method.formalReturns) - .map(vars => vars._2.asInstanceOf[Declaration] -> vars._1.asInstanceOf[Declaration]).toMap + (AnalysisUtils.heapVertex -> AnalysisUtils.heapVertex) + (AssumeNode(method.pos) -> AssumeNode(pos)) + .map(vars => vars._2.asInstanceOf[Declaration] -> vars._1.asInstanceOf[Declaration]).toMap + (AnalysisUtils.heapVertex -> AnalysisUtils.heapVertex) + (AssumeMethodNode(method.pos) -> AssumeMethodNode(pos)) val resolvedMethodMap = getMethodAnalysisMap(method) .filter(v => retVarMapping.contains(v._1)) @@ -340,12 +345,12 @@ case class VarAnalysisGraphMap(prog: Program, case MethodCall(name, _, targets) => val methodInfluences = getMethodAnalysisMap(prog.methodsByName(name)) if(methodInfluences.exists(v => v._1.isInstanceOf[AssumeNode])) { - targets.map(getLocalVarDeclFromLocalVar).toSet + AssumeNode(stmt.pos) + targets.map(getLocalVarDeclFromLocalVar).toSet + AssumeMethodNode(stmt.pos) } else { targets.map(getLocalVarDeclFromLocalVar).toSet } - case i: Inhale => Set(AssumeNode(i.pos)) - case a: Assume => Set(AssumeNode(a.pos)) + case i: Inhale => Set(AssumeStmtNode(i.pos)) + case a: Assume => Set(AssumeStmtNode(a.pos)) case Label(_, _) => Set() case Quasihavoc(_, _) => Set(heapVertex) case Quasihavocall(_, _, _) => Set(heapVertex) From ab3e166e4c0f6e1cdf8bd34c97981a3971bf76aa Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Mon, 24 Jun 2024 09:56:54 +0200 Subject: [PATCH 35/41] split veforeVerify method --- .../standard/reasoning/ReasoningPlugin.scala | 248 +++++++++--------- 1 file changed, 125 insertions(+), 123 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index 8cb02cd49..b5f1116b1 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -134,6 +134,117 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, } + private def removeReasoningAnnotations(m: Method): Method = { + val flowAnnotationAndLemmaFilter: Exp => Boolean = { + case _: FlowAnnotation | _: Lemma | _: NoAssumeAnnotation => false + case _ => true + } + val postconds = m.posts.filter(flowAnnotationAndLemmaFilter) + val preconds = m.pres.filter(flowAnnotationAndLemmaFilter) + m.copy(pres = preconds, posts = postconds)(m.pos, m.info, m.errT) + } + + private def translateOldCall(o: OldCall, method: Method, usedNames: mutable.Set[String]): Stmt = { + val new_v_map: Seq[(LocalVarDecl, Exp)] = ((method.formalArgs ++ method.formalReturns) zip (o.args ++ o.rets)).map(zipped => { + val formal_a: LocalVarDecl = zipped._1 + val arg_exp: Exp = zipped._2 + formal_a -> arg_exp + }) + + /** replace all input parameters in preconditions with the corresponding argument */ + val new_pres = method.pres.flatMap { + case Lemma() => Seq.empty + case p => Seq(applySubstitutionWithExp(new_v_map, p)) + } + + /** replace all input & output parameters in postconditions with the corresponding argument / result */ + val new_posts = method.posts.flatMap { + case Lemma() => Seq.empty + case p => Seq(applySubstitutionWithExp(new_v_map, p)) + } + + /** create new variable declarations to havoc the lhs of the oldCall */ + val rToV = o.rets.map(r => { + val new_v = LocalVarDecl(uniqueName(".v", usedNames), r.typ)(r.pos) + r -> new_v + }).toMap + + val errTransformer = ErrTrafo({ + case AssertFailed(_, r, c) => PreconditionInLemmaCallFalse(o, r, c) + case d => d + }) + + Seqn( + // assert precondition(s) + new_pres.map(p => + Assert(LabelledOld(p, o.oldLabel)(p.pos))(o.pos, errT = errTransformer) + ) ++ + // havoc return args by assigning an unconstrained value + o.rets.map(r => { + LocalVarAssign(r, rToV(r).localVar)(o.pos) + }) ++ + // inhale postcondition(s) + new_posts.map(p => + Inhale(LabelledOld(p, o.oldLabel)(p.pos))(o.pos) + ), + rToV.values.toSeq + )(o.pos) + } + + private def translateExistentialElim(e: ExistentialElim, usedNames: mutable.Set[String]): Stmt = { + val (new_v_map, new_exp) = substituteWithFreshVars(e.varList, e.exp, usedNames) + val new_trigs = e.trigs.map(t => Trigger(t.exps.map(e1 => applySubstitution(new_v_map, e1)))(t.pos)) + val errTransformer = ErrTrafo({ + case AssertFailed(_, r, c) => ExistentialElimFailed(e, r, c) + case d => d + }) + Seqn( + Seq( + Assert(Exists(new_v_map.map(_._2), new_trigs, new_exp)(e.pos, errT = errTransformer))(e.pos) + ) + ++ + e.varList.map(variable => LocalVarDeclStmt(variable)(variable.pos)) //list of variables + ++ + Seq( + Inhale(e.exp)(e.pos) + ), + Seq() + )(e.pos) + } + + private def translateUniversalIntroduction(u: UniversalIntro, usedNames: mutable.Set[String]): Stmt = { + /** Translate the new syntax into Viper language */ + val (newVarMap, newExp1) = substituteWithFreshVars(u.varList, u.assumingExp, usedNames) + val newExp2 = applySubstitution(newVarMap, u.implyingExp) + val quantifiedVars = newVarMap.map(vars => vars._2) + val newTrigs = u.triggers.map(t => Trigger(t.exps.map(e1 => applySubstitution(newVarMap, e1)))(t.pos)) + val lbl = uniqueName("l", usedNames) + val errTransformer = ErrTrafo({ + case AssertFailed(_, r, c) => UniversalIntroFailed(u, r, c) + case d => d + }) + + val boolvar = LocalVarDecl(uniqueName("b", usedNames), Bool)(u.assumingExp.pos) + Seqn( + Seq( + Label(lbl, Seq.empty)(u.pos), + // conditionally inhale assume expression + If(boolvar.localVar, + Seqn( + Seq(Inhale(u.assumingExp)(u.assumingExp.pos)), + Seq.empty)(u.assumingExp.pos), + Seqn(Seq.empty, Seq.empty)(u.assumingExp.pos) + )(u.assumingExp.pos), + // execute block + u.block, + // conditionally assert imply expression + Assert(Implies(boolvar.localVar, u.implyingExp)(u.implyingExp.pos))(u.implyingExp.pos, errT = errTransformer), + Inhale(Forall(quantifiedVars, newTrigs, Implies(LabelledOld(newExp1, lbl)(u.implyingExp.pos), newExp2)(u.implyingExp.pos))(u.implyingExp.pos))(u.implyingExp.pos) + ), + Seq(boolvar) ++ u.varList + )(u.assumingExp.pos) + } + override def beforeVerify(input: Program): Program = { val usedNames: mutable.Set[String] = collection.mutable.Set(input.transitiveScopedDecls.map(_.name): _*) @@ -146,104 +257,18 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, analysis.checkUserProvidedInfluencesSpec() ViperStrategy.Slim({ - /** remove the influenced by postconditions. * remove isLemma */ - case m: Method => - val flowAnnotationAndLemmaFilter: Exp => Boolean = { - case _: FlowAnnotation | _: Lemma => false - case _ => true - } - val postconds = m.posts.filter(flowAnnotationAndLemmaFilter) - val preconds = m.pres.filter(flowAnnotationAndLemmaFilter) - if (postconds != m.posts || preconds != m.pres) { - m.copy(pres = preconds, posts = postconds)(m.pos, m.info, m.errT) - } else { - m - } - - case o@OldCall(methodName, args, rets, lbl) => + case m: Method => removeReasoningAnnotations(m) + case o: OldCall => /** check whether called method is a lemma */ - val currmethod = input.findMethod(methodName) - val isLemma = specifiesLemma(currmethod) - - if (!isLemma) { + val currmethod = input.findMethod(o.methodName) + if (!specifiesLemma(currmethod)) { reportError(ConsistencyError(s"method ${currmethod.name} called in old context must be lemma", o.pos)) } - - val new_v_map: Seq[(LocalVarDecl, Exp)] = ((currmethod.formalArgs ++ currmethod.formalReturns) zip (args ++ rets)).map(zipped => { - val formal_a: LocalVarDecl = zipped._1 - val arg_exp: Exp = zipped._2 - formal_a -> arg_exp - }) - - /** replace all input parameters in preconditions with the corresponding argument */ - val new_pres = currmethod.pres.flatMap { - case Lemma() => Seq.empty - case p => Seq(applySubstitutionWithExp(new_v_map, p)) - } - - /** replace all input & output parameters in postconditions with the corresponding argument / result */ - val new_posts = currmethod.posts.flatMap { - case Lemma() => Seq.empty - case p => Seq(applySubstitutionWithExp(new_v_map, p)) - } - - /** create new variable declarations to havoc the lhs of the oldCall */ - val rToV = rets.map(r => { - val new_v = LocalVarDecl(uniqueName(".v", usedNames),r.typ)(r.pos) - r -> new_v - }).toMap - - val errTransformer = ErrTrafo({ - case AssertFailed(_, r, c) => PreconditionInLemmaCallFalse(o, r, c) - case d => d - }) - - Seqn( - // assert precondition(s) - new_pres.map(p => - Assert(LabelledOld(p, lbl)(p.pos))(o.pos, errT = errTransformer) - ) ++ - // havoc return args by assigning an unconstrained value - rets.map(r => { - LocalVarAssign(r, rToV(r).localVar)(o.pos) - }) ++ - // inhale postcondition(s) - new_posts.map(p => - Inhale(LabelledOld(p, lbl)(p.pos))(o.pos) - ), - rToV.values.toSeq - )(o.pos) - - case e@ExistentialElim(v, trigs, exp) => - val (new_v_map, new_exp) = substituteWithFreshVars(v, exp, usedNames) - val new_trigs = trigs.map(t => Trigger(t.exps.map(e1 => applySubstitution(new_v_map, e1)))(t.pos)) - val errTransformer = ErrTrafo({ - case AssertFailed(_, r, c) => ExistentialElimFailed(e, r, c) - case d => d - }) - Seqn( - Seq( - Assert(Exists(new_v_map.map(_._2), new_trigs, new_exp)(e.pos, errT = errTransformer))(e.pos) - ) - ++ - v.map(variable => LocalVarDeclStmt(variable)(variable.pos)) //list of variables - ++ - Seq( - Inhale(exp)(e.pos) - ), - Seq() - )(e.pos) - - case u@UniversalIntro(v, trigs, exp1, exp2, blk) => - val boolvar = LocalVarDecl(uniqueName("b", usedNames), Bool)(exp1.pos) - - /** Get all variables that are in scope in the current method */ - val tainted: Set[LocalVarDecl] = v.toSet - val varsOutside = (input.methods - .filter(m => m.body.isDefined) - .flatMap(m => m.body.get.ss.filter(s => s.contains(u)).flatMap(_ => Set(m.transitiveScopedDecls: _*))).toSet -- Set(u.transitiveScopedDecls: _*)) ++ tainted + translateOldCall(o, currmethod, usedNames) + case e: ExistentialElim => translateExistentialElim(e, usedNames) + case u@UniversalIntro(v, _, exp1, exp2, blk) => /** * get all variables that are assigned to inside the block and take intersection with universal introduction * variables. If they are contained throw error since quantified variables should be immutable @@ -252,40 +277,17 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, checkReassigned(writtenVars, v, reportError, u) checkInfluencedBy(input, reportError) + /** Get all variables that are in scope in the current method */ + val tainted: Set[LocalVarDecl] = v.toSet + val varsOutside = (input.methods + .filter(m => m.body.isDefined) + .flatMap(m => m.body.get.ss.filter(s => s.contains(u)).flatMap(_ => Set(m.transitiveScopedDecls: _*))).toSet -- Set(u.transitiveScopedDecls: _*)) ++ tainted + /** Contains all variables that must not be tainted */ val volatileVars: Set[LocalVarDecl] = analysis.getLocalVarDeclsFromExpr(exp1) ++ analysis.getLocalVarDeclsFromExpr(exp2) -- v /** execute modular flow analysis using graph maps for the universal introduction statement */ analysis.executeTaintedGraphAnalysis(varsOutside.collect({ case v:LocalVarDecl => v }), tainted, blk, volatileVars, u) - - /** Translate the new syntax into Viper language */ - val (newVarMap, newExp1) = substituteWithFreshVars(v, exp1, usedNames) - val newExp2 = applySubstitution(newVarMap, exp2) - val quantifiedVars = newVarMap.map(vars => vars._2) - val newTrigs = trigs.map(t => Trigger(t.exps.map(e1 => applySubstitution(newVarMap, e1)))(t.pos)) - val lbl = uniqueName("l", usedNames) - val errTransformer = ErrTrafo({ - case AssertFailed(_, r, c) => UniversalIntroFailed(u, r, c) - case d => d - }) - - Seqn( - Seq( - Label(lbl, Seq.empty)(u.pos), - // conditionally inhale assume expression - If(boolvar.localVar, - Seqn( - Seq(Inhale(exp1)(exp1.pos)), - Seq.empty)(exp1.pos), - Seqn(Seq.empty, Seq.empty)(exp1.pos) - )(exp1.pos), - // execute block - blk, - // conditionally assert imply expression - Assert(Implies(boolvar.localVar, exp2)(exp2.pos))(exp2.pos, errT = errTransformer), - Inhale(Forall(quantifiedVars, newTrigs, Implies(LabelledOld(newExp1, lbl)(exp2.pos), newExp2)(exp2.pos))(exp2.pos))(exp2.pos) - ), - Seq(boolvar) ++ v - )(exp1.pos) + translateUniversalIntroduction(u, usedNames); }, Traverse.TopDown).execute[Program](input) } } From 20cf3e9350702e94259c071e7cf45472ede62a20 Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Mon, 24 Jun 2024 09:57:09 +0200 Subject: [PATCH 36/41] add assume to pretty print --- .../plugin/standard/reasoning/ReasoningASTExtension.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala index 5b99a8dbf..374d37507 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala @@ -120,7 +120,8 @@ case class FlowAnnotation(v: FlowVar, varList: Seq[FlowVarOrHeap])(val pos: Posi override def prettyPrint: PrettyPrintPrimitives#Cont = { text("influenced") <+> (v match { case value: Var => show(value.decl) - case _ => text("heap") + case _: Assumes => text("assumes") + case _: Heap => text("heap") }) <+> text("by") <+> ssep(varList.map { From f0476dac2c22430735472fe613c2e38a5419d9bd Mon Sep 17 00:00:00 2001 From: Linard Arquint Date: Sun, 30 Jun 2024 23:39:18 +0200 Subject: [PATCH 37/41] improves type guarantees of modular taint analysis and fixes termination checks therein --- .../reasoning/BeforeVerifyHelper.scala | 65 ++- .../reasoning/ReasoningASTExtension.scala | 8 +- .../reasoning/ReasoningPASTExtension.scala | 19 +- .../standard/reasoning/ReasoningPlugin.scala | 25 +- .../analysis/VarAnalysisGraphMap.scala | 504 +++++++++++------- .../reasoning/existential_elim_fail1.vpr | 11 + ...elim.vpr => existential_elim_success1.vpr} | 13 +- .../{old_call.vpr => old_call_fail1.vpr} | 4 +- .../resources/reasoning/old_call_fail2.vpr | 12 + .../resources/reasoning/old_call_success1.vpr | 23 + 10 files changed, 430 insertions(+), 254 deletions(-) create mode 100644 src/test/resources/reasoning/existential_elim_fail1.vpr rename src/test/resources/reasoning/{existential_elim.vpr => existential_elim_success1.vpr} (75%) rename src/test/resources/reasoning/{old_call.vpr => old_call_fail1.vpr} (73%) create mode 100644 src/test/resources/reasoning/old_call_fail2.vpr create mode 100644 src/test/resources/reasoning/old_call_success1.vpr diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala index 20469ea34..1a268c33c 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala @@ -8,9 +8,10 @@ package viper.silver.plugin.standard.reasoning import viper.silver.ast.utility.Expressions -import viper.silver.ast.{Apply, Declaration, Exhale, Exp, FieldAssign, Fold, Inhale, LocalVarDecl, Method, MethodCall, Package, Program, Seqn, Stmt, Unfold} +import viper.silver.ast.{Apply, Exhale, Exp, FieldAssign, Fold, Inhale, LocalVarDecl, Method, MethodCall, Package, Program, Seqn, Stmt, Unfold} import viper.silver.verifier.{AbstractError, ConsistencyError} import viper.silver.plugin.standard.reasoning.analysis.AnalysisUtils +import viper.silver.plugin.standard.reasoning.analysis.AnalysisUtils.{AssumeInfluenceSink, InfluenceSink} import scala.collection.mutable @@ -71,21 +72,20 @@ trait BeforeVerifyHelper { def checkLemmas(input: Program, reportError: AbstractError => Unit): Unit = { input.methods.foreach(method => { val containsLemma = specifiesLemma(method) - val (containsDecreases, containsDecreasesStar) = AnalysisUtils.specifiesTermination(method) + val terminationSpecState = AnalysisUtils.specifiesTermination(method) if (containsLemma) { - /** report error if there is no decreases clause or specification */ - if(!containsDecreases) { - reportError(ConsistencyError(s"Lemmas must terminate but method ${method.name} marked lemma does not specify any termination measures", method.pos)) - } - - /** report error if the decreases statement might not prove termination */ - if (containsDecreasesStar) { - reportError(ConsistencyError("Lemmas must terminate but method ${method.name} marked lemma specifies only incomplete termination measures", method.pos)) + // lemmas must terminate. We report slightly different errors depending on the cause: + if (!terminationSpecState.guaranteesTermination) { + if (terminationSpecState.noTerminationSpec) { + reportError(ConsistencyError(s"Lemmas must terminate but method ${method.name} marked lemma does not specify any termination measures", method.pos)) + } else { + reportError(ConsistencyError(s"Lemmas must terminate but the specification of method ${method.name} might not guarantee termination", method.pos)) + } } /** check method body for impure statements */ - checkStmtPure(method.body.getOrElse(Seqn(Seq(), Seq())()), method, input, reportError) + checkStmtPure(method.body.getOrElse(Seqn(Seq.empty, Seq.empty)()), method, input, reportError) } }) } @@ -116,31 +116,48 @@ trait BeforeVerifyHelper { /** checks that all influences by annotations in `input` are used correctly. */ def checkInfluencedBy(input: Program, reportError: AbstractError => Unit): Unit = { input.methods.foreach(method => { - val argVars = method.formalArgs.toSet + AnalysisUtils.heapVertex - val retVars = method.formalReturns.toSet.asInstanceOf[Set[Declaration]] + AnalysisUtils.heapVertex + AnalysisUtils.AssumeMethodNode(method.pos) + val argSources = method.formalArgs.map(AnalysisUtils.getSourceFromVarDecl).toSet + AnalysisUtils.HeapSource + val retSinks = method.formalReturns.map(AnalysisUtils.getSinkFromVarDecl).toSet + AnalysisUtils.HeapSink + AnalysisUtils.AssumeMethodInfluenceSink(method) - val seenVars: mutable.Set[Declaration] = mutable.Set() + val seenSinks: mutable.Set[InfluenceSink] = mutable.Set.empty /** iterate through method postconditions to find flow annotations */ method.posts.foreach { - case v@FlowAnnotation(target, args) => - val targetVarDecl = AnalysisUtils.getDeclarationFromFlowVar(target, method) + case v@InfluencedBy(target, args) => + val declaredSink = AnalysisUtils.getSinkFromFlowVar(target, method) - if (!retVars.contains(targetVarDecl)) { - reportError(ConsistencyError(s"Only return parameters, the heap or assumes can be influenced. ${targetVarDecl.name} is not be a return parameter.", v.pos)) + if (!retSinks.contains(declaredSink)) { + reportError(ConsistencyError(s"Only return parameters, the heap or assumes can be influenced but not ${declaredSink.name}.", v.pos)) } - if (seenVars.contains(targetVarDecl)) { - reportError(ConsistencyError(s"Only one influenced by expression per return parameter can exist. ${targetVarDecl.name} is used several times.", v.pos)) + if (seenSinks.contains(declaredSink)) { + reportError(ConsistencyError(s"Only one influenced-by specification per return parameter, heap or assume is allowed. ${declaredSink.name} is used several times.", v.pos)) } - seenVars.add(targetVarDecl) + seenSinks.add(declaredSink) args.foreach(arg => { - val argVarDecl = AnalysisUtils.getLocalVarDeclFromFlowVar(arg) - if (!argVars.contains(argVarDecl)) { - reportError(ConsistencyError(s"Only method input parameters or the heap can influence a return parameter. ${argVarDecl.name} is not be a method input parameter.", v.pos)) + val declaredSource = AnalysisUtils.getSourceFromFlowVar(arg) + if (!argSources.contains(declaredSource)) { + reportError(ConsistencyError(s"Only method input parameters or the heap can be sources of influenced-by specifications but not ${declaredSource.name}.", v.pos)) } }) case _ => } + + // checks that "assume influeced by" and "assumesNothing" are mutually exclusive: + val assumeNothings = method.posts.collect { + // case InfluencedBy(_: Assumes, _) => + case a: AssumesNothing => a + } + val hasAssumeInfluenceSink = seenSinks.exists { + case _: AssumeInfluenceSink => true + case _ => false + } + if (assumeNothings.length > 1) { + assumeNothings.foreach(a => + reportError(ConsistencyError(s"At most one '${PNothingKeyword.keyword}' permitted per method specification.", a.pos))) + } else if (assumeNothings.nonEmpty && hasAssumeInfluenceSink) { + assumeNothings.foreach(a => + reportError(ConsistencyError(s"'${PNothingKeyword.keyword}' and '${PInfluencedKeyword.keyword} ${PAssumesKeyword.keyword} ${PByKeyword.keyword} ...' are mutually exclusive.", a.pos))) + } }) } } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala index 374d37507..41cae3162 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningASTExtension.scala @@ -82,7 +82,9 @@ case class Var(decl: LocalVar)(val pos: Position = NoPosition, val info: Info = override def prettyPrint: PrettyPrintPrimitives#Cont = show(decl) } -case class NoAssumeAnnotation()(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionExp with Node { +trait FlowAnnotation extends ExtensionExp with Node + +case class AssumesNothing()(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowAnnotation { override def extensionIsPure: Boolean = true override def extensionSubnodes: Seq[Node] = Seq() @@ -97,11 +99,11 @@ case class NoAssumeAnnotation()(val pos: Position = NoPosition, val info: Info = /** Pretty printing functionality as defined for other nodes in class FastPrettyPrinter. * Sample implementation would be text("old") <> parens(show(e)) for pretty-printing an old-expression. */ override def prettyPrint: PrettyPrintPrimitives#Cont = { - text("assumes nothing") + text(PNothingKeyword.keyword) } } -case class FlowAnnotation(v: FlowVar, varList: Seq[FlowVarOrHeap])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionExp with Node with Scope { +case class InfluencedBy(v: FlowVar, varList: Seq[FlowVarOrHeap])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends FlowAnnotation with Scope { override def extensionIsPure: Boolean = true override val scopedDecls: Seq[Declaration] = Seq() diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala index 04676c199..ae82d4df4 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -64,7 +64,11 @@ case class PUniversalIntro(proveKw: PReserved[PProveKeyword.type], forallKw: PKw } } -case class PFlowAnnotation(v: PFlowVar, byKw: PReserved[PByKeyword.type], groupedVarList: PGrouped[PSym.Brace, Seq[PFlowVarOrHeap]])(val pos: (Position,Position)) extends PExtender with PExp { +trait PFlowAnnotation extends PExp with PExtender { + val pos: (Position,Position) +} + +case class PInfluencedBy(v: PFlowVar, byKw: PReserved[PByKeyword.type], groupedVarList: PGrouped[PSym.Brace, Seq[PFlowVarOrHeap]])(val pos: (Position,Position)) extends PFlowAnnotation { lazy val varList: Seq[PFlowVarOrHeap] = groupedVarList.inner override def typeSubstitutions: Seq[PTypeSubstitution] = Seq(PTypeSubstitution.id) @@ -88,7 +92,7 @@ case class PFlowAnnotation(v: PFlowVar, byKw: PReserved[PByKeyword.type], groupe } override def translateExp(t: Translator): ExtensionExp = { - FlowAnnotation(v.translate(t), varList.map { variable => variable.translate(t) })(t.liftPos(this)) + InfluencedBy(v.translate(t), varList.map { variable => variable.translate(t) })(t.liftPos(this)) } } @@ -125,15 +129,15 @@ case class PAssumes(assumes: PReserved[PAssumesKeyword.type])(val pos: (Position override def pretty: String = PAssumesKeyword.keyword } -case class PAssumesNothing()(val pos: (Position,Position)) extends PExp with PExtender { - def translate(t: Translator): NoAssumeAnnotation = { - NoAssumeAnnotation()(t.liftPos(this)) +case class PAssumesNothing()(val pos: (Position,Position)) extends PFlowAnnotation { + def translate(t: Translator): AssumesNothing = { + AssumesNothing()(t.liftPos(this)) } - override def pretty: String = PAssumesKeyword.keyword + override def pretty: String = PNothingKeyword.keyword override def typecheck(t: TypeChecker, n: NameAnalyser, expected: PType): Option[Seq[String]] = None - override def translateExp(t: Translator): Exp = NoAssumeAnnotation()(pos._1) + override def translateExp(t: Translator): Exp = AssumesNothing()(t.liftPos(this)) override def typeSubstitutions: collection.Seq[PTypeSubstitution] = Seq(PTypeSubstitution.id) @@ -221,6 +225,7 @@ case class POldCallExp(oldCallKw: PReserved[POldCallKeyword.type], lbl: PGrouped // this node should get translated to `POldCall` but `beforeResolve` in `ReasoningPlugin` performs this translation // only if its parent node is a PAssign. Thus, an invocation of this function indicates that this expression occurs // at an unsupported location within the AST. + // TODO check that label is valid Some(Seq(s"oldCalls are only supported as statements or as the right-hand side of an assignment")) } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index b5f1116b1..9e19a3bc6 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -10,8 +10,9 @@ import fastparse._ import viper.silver.ast._ import viper.silver.ast.utility.rewriter.{StrategyBuilder, Traverse} import viper.silver.ast.utility.ViperStrategy -import viper.silver.parser.FastParserCompanion.whitespace +import viper.silver.parser.FastParserCompanion.{Pos, whitespace} import viper.silver.parser._ +import viper.silver.plugin.standard.reasoning.analysis.AnalysisUtils.{LocalVarSink, NonAssumeInfluenceSink} import viper.silver.plugin.standard.reasoning.analysis.VarAnalysisGraphMap import viper.silver.plugin.{ParserPluginTemplate, SilverPlugin} import viper.silver.verifier._ @@ -49,23 +50,25 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, def assumes[$: P]: P[PAssumes] = P(P(PAssumesKeyword) map (PAssumes(_) _)).pos // note that the parentheses are not redundant def assumesNothingSpec[$: P]: P[PSpecification[PNothingKeyword.type]] = - P((P(PNothingKeyword) ~ assumesNothingClause) map (PSpecification.apply _).tupled).pos + P((P(PNothingKeyword) ~ assumesNothingClause) map { case (kw, clauseFn) => pos: Pos => PSpecification(kw, clauseFn(pos))(pos) }).pos - // assumes nothing clause is completely artificial and is created out of nowhere at the parser's current position - def assumesNothingClause[$: P]: P[PAssumesNothing] = (Pass(()) map { _ => PAssumesNothing()(_) }).pos + // assumes nothing clause is completely artificial and is created out of nowhere. Instead of taking the parser's current position, + // we parameterize this parser by `Pos` such that the caller can pass in the position of, e.g., the corresponding keyword. + def assumesNothingClause[$: P]: P[Pos => PAssumesNothing] = Pass(()) map { _ => PAssumesNothing()(_) } def singleVar[$: P]: P[PVar] = P(fp.idnuse map (PVar(_) _)).pos // note that the parentheses are not redundant def varsAndHeap[$: P]: P[Seq[PFlowVarOrHeap]] = (heap | singleVar).delimited(PSym.Comma).map(_.toSeq) - def influencedBy[$: P]: P[PFlowAnnotation] = P(((heap | assumes | singleVar) ~ P(PByKeyword) ~/ varsAndHeap.braces) map { - case (v, byKw, groupedVarList) => PFlowAnnotation(v, byKw, groupedVarList)(_) + def influencedBy[$: P]: P[PInfluencedBy] = P(((heap | assumes | singleVar) ~ P(PByKeyword) ~/ varsAndHeap.braces) map { + case (v, byKw, groupedVarList) => PInfluencedBy(v, byKw, groupedVarList)(_) }).pos /** parser for lemma annotation */ def lemma[$: P]: P[PSpecification[PIsLemmaKeyword.type]] = - P((P(PIsLemmaKeyword) ~ lemmaClause) map (PSpecification.apply _).tupled).pos + P((P(PIsLemmaKeyword) ~ lemmaClause) map { case (kw, clauseFn) => pos: Pos => PSpecification(kw, clauseFn(pos))(pos) }).pos - // lemma clause is completely artificial and is created out of nowhere at the parser's current position - def lemmaClause[$: P]: P[PLemmaClause] = (Pass(()) map { _ => PLemmaClause()(_) }).pos + // lemma clause is completely artificial and is created out of nowhere. Instead of taking the parser's current position, + // we parameterize this parser by `Pos` such that the caller can pass in the position of, e.g., the corresponding keyword. + def lemmaClause[$: P]: P[Pos => PLemmaClause] = Pass(()) map { _ => PLemmaClause()(_) } /** parsers for oldCall statement */ /* @@ -136,7 +139,7 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, private def removeReasoningAnnotations(m: Method): Method = { val flowAnnotationAndLemmaFilter: Exp => Boolean = { - case _: FlowAnnotation | _: Lemma | _: NoAssumeAnnotation => false + case _: InfluencedBy | _: Lemma | _: AssumesNothing => false case _ => true } val postconds = m.posts.filter(flowAnnotationAndLemmaFilter) @@ -284,7 +287,7 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, .flatMap(m => m.body.get.ss.filter(s => s.contains(u)).flatMap(_ => Set(m.transitiveScopedDecls: _*))).toSet -- Set(u.transitiveScopedDecls: _*)) ++ tainted /** Contains all variables that must not be tainted */ - val volatileVars: Set[LocalVarDecl] = analysis.getLocalVarDeclsFromExpr(exp1) ++ analysis.getLocalVarDeclsFromExpr(exp2) -- v + val volatileVars: Set[NonAssumeInfluenceSink] = analysis.getSinksFromExpr(exp1) ++ analysis.getSinksFromExpr(exp2) -- v.map(decl => LocalVarSink(decl.localVar)).toSet /** execute modular flow analysis using graph maps for the universal introduction statement */ analysis.executeTaintedGraphAnalysis(varsOutside.collect({ case v:LocalVarDecl => v }), tainted, blk, volatileVars, u) translateUniversalIntroduction(u, usedNames); diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index 741701339..ae76e991c 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -7,69 +7,132 @@ package viper.silver.plugin.standard.reasoning.analysis import viper.silver.ast.utility.Expressions -import viper.silver.ast.{AccessPredicate, Apply, Assert, Assume, BinExp, CurrentPerm, Declaration, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, Fold, ForPerm, FuncApp, Goto, If, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, Method, MethodCall, Package, Position, Program, Quasihavoc, Quasihavocall, Ref, Scope, Seqn, Stmt, UnExp, Unfold, While} -import viper.silver.plugin.standard.reasoning.analysis.AnalysisUtils.{AssumeMethodNode, AssumeNode, AssumeStmtNode, AssumeWhileNode, getLocalVarDeclFromLocalVar, heapVertex} -import viper.silver.plugin.standard.reasoning.{Assumes, ExistentialElim, FlowAnnotation, FlowVar, FlowVarOrHeap, Heap, NoAssumeAnnotation, OldCall, UniversalIntro, Var} -import viper.silver.plugin.standard.termination.{DecreasesSpecification, DecreasesStar, DecreasesTuple, DecreasesWildcard} +import viper.silver.ast.{AccessPredicate, Apply, Assert, Assume, BinExp, CurrentPerm, Declaration, DomainFuncApp, Exhale, Exp, FieldAccess, FieldAssign, Fold, ForPerm, FuncApp, Goto, If, Info, Inhale, Label, LocalVar, LocalVarAssign, LocalVarDecl, Method, MethodCall, NoPosition, Package, Position, Program, Quasihavoc, Quasihavocall, Scope, Seqn, Stmt, UnExp, Unfold, While} +import viper.silver.plugin.standard.reasoning.analysis.AnalysisUtils.{AssumeInfluenceSink, AssumeMethodInfluenceSink, AssumeStmtInfluenceSink, AssumeWhileInfluenceSink, HeapSink, HeapSource, InfluenceSink, InfluenceSource, LocalVarSink, LocalVarSource, NonAssumeInfluenceSink, getLocalVarDeclFromLocalVar, getSinkFromVarDecl, getSourceFromVarDecl} +import viper.silver.plugin.standard.reasoning.{Assumes, AssumesNothing, ExistentialElim, FlowAnnotation, FlowVar, FlowVarOrHeap, Heap, InfluencedBy, OldCall, UniversalIntro, Var} +import viper.silver.plugin.standard.termination.{DecreasesClause, DecreasesSpecification, DecreasesTuple, DecreasesWildcard} import viper.silver.verifier.{AbstractError, ConsistencyError} import scala.collection.mutable object AnalysisUtils { - // TODO add traits for kind of assume sources(e.g assume/inhale stmt, method call, while loop) - // Position is used for identifying a source for an assume(assume stmt, method call, while loop etc.) and for printing error messages - trait AssumeNode extends Declaration { + trait InfluenceSource extends Declaration + + case object HeapSource extends InfluenceSource { + override def name: String = ".heap" + override def pos: Position = NoPosition + } + + /** This is either a local variable or a method's input parameter */ + case class LocalVarSource(v: LocalVar) extends InfluenceSource { + override def name: String = v.name + + override def pos: Position = v.pos + } + + def getSourceFromFlowVar(f: FlowVarOrHeap): InfluenceSource = f match { + case v: Var => LocalVarSource(v.decl) + case _: Heap => HeapSource + } + + def getSourceFromVarDecl(v: LocalVarDecl): InfluenceSource = LocalVarSource(v.localVar) + + /** presence of an InfluenceSink indicates that assume stmts are potentially (transitively) present.This sink might + * map to an emtpy set of sources in case these assume stmts are not influenced by any variables. Note that we use + * the absence of these sinks in the GraphMap to indicate that no assume stmts are (transitively) present. + */ + trait InfluenceSink extends Declaration + + trait AssumeInfluenceSink extends InfluenceSink { override def name: String = ".assume" + def pretty: String } - case class AssumeMethodNode(pos: Position) extends AssumeNode - case class AssumeWhileNode(pos: Position) extends AssumeNode - case class AssumeStmtNode(pos: Position) extends AssumeNode - val heapVertex: LocalVarDecl = LocalVarDecl(".heap", Ref)() + /** Declaration that method might contain assume stmts, which is used for the analysis of the callers of this method */ + case class AssumeMethodInfluenceSink(m: Method) extends AssumeInfluenceSink { + lazy val pretty = s"Method called at $pos" + override def pos: Position = m.pos + } + /** Declaration that the while loop behaves like an assume stmt, which is used for the analysis of the surrounding method */ + case class AssumeWhileInfluenceSink(w: While) extends AssumeInfluenceSink { + lazy val pretty = s"Loop at $pos" + override def pos: Position = w.pos + } + /** Declaration that there's an assume or inhale stmt at `pos`, which is used for the analysis of the surrounding method */ + case class AssumeStmtInfluenceSink(i: Inhale) extends AssumeInfluenceSink { + lazy val pretty = s"Assume or inhale at $pos" + override def pos: Position = i.pos + } - def getDeclarationFromFlowVar(f: FlowVar, m: Method): Declaration = { - f match { - case v: Var => LocalVarDecl(v.decl.name, v.decl.typ)(v.decl.pos) - case _: Heap => heapVertex - case _: Assumes => AssumeMethodNode(m.pos) - } + trait NonAssumeInfluenceSink extends InfluenceSink + + case object HeapSink extends NonAssumeInfluenceSink { + override def name: String = ".heap" + override def pos: Position = NoPosition } - def getLocalVarDeclFromFlowVar(f: FlowVarOrHeap): LocalVarDecl = { - f match { - case v: Var => LocalVarDecl(v.decl.name, v.decl.typ)(v.decl.pos) - case _: Heap => heapVertex + /** This is either a local variable or a method's output parameter */ + case class LocalVarSink(v: LocalVar) extends NonAssumeInfluenceSink { + override def name: String = v.name + override def pos: Position = v.pos + } + + def getSinkFromFlowVar(f: FlowVar, m: Method): InfluenceSink = f match { + case f: FlowVarOrHeap => f match { + case v: Var => LocalVarSink(v.decl) + case _: Heap => HeapSink } + case _: Assumes => AssumeMethodInfluenceSink(m) + } + + def getSinkFromVarDecl(v: LocalVarDecl): InfluenceSink = LocalVarSink(v.localVar) + + def isAssumeSink(sink: InfluenceSink): Boolean = sink match { + case _: AssumeInfluenceSink => true + case _: NonAssumeInfluenceSink => false } def getLocalVarDeclFromLocalVar(l: LocalVar): LocalVarDecl = { LocalVarDecl(l.name, l.typ)() } - def specifiesTermination(m: Method): (Boolean, Boolean) = { - val presContain = m.pres.collect({ case DecreasesTuple(_, _) | DecreasesWildcard(_) => true }).nonEmpty - val postsContain = m.posts.collect({ case DecreasesTuple(_, _) | DecreasesWildcard(_) => true }).nonEmpty + case class TerminationSpecState(guaranteesTermination: Boolean, noTerminationSpec: Boolean) - val presContainStar = m.pres.collect({ case DecreasesStar() => true }).nonEmpty - val postsContainStar = m.posts.collect({ case DecreasesStar() => true }).nonEmpty + private def specifiesTermination(spec: Seq[Exp], info: Info): TerminationSpecState = { + // we do not analyze whether the conditions cover all cases but are conservative. + val noTerminationSpec = spec.forall { + case _: DecreasesClause => false + case _ => true + } + val allInfos = info.getAllInfos[Info] + val noTerminationInfo = allInfos.forall { + case _: DecreasesSpecification => false + case _ => true + } - /** check info for decreases specification */ - val infoContains = m.meta._2 match { - case _: DecreasesSpecification => true + def specifiesCompleteTermination(e: Exp): Boolean = e match { + case DecreasesTuple(_, None) => true + case DecreasesWildcard(None) => true case _ => false } - val infoContainsStar = m.meta._2 match { - case spec: DecreasesSpecification => spec.star.isDefined + val isTerminationSpecComplete = spec.exists(specifiesCompleteTermination) + + val isTerminationInfoComplete = allInfos.exists { + case DecreasesSpecification(optTuple, optWildcard, _) => + optTuple.exists(specifiesCompleteTermination) || optWildcard.exists(specifiesCompleteTermination) case _ => false } - val containsTerminationMeasure = presContain | postsContain | presContainStar | postsContainStar | infoContains | infoContainsStar - val mightNotTerminate = !containsTerminationMeasure | presContainStar | postsContainStar | infoContainsStar - - (containsTerminationMeasure, mightNotTerminate) + TerminationSpecState( + guaranteesTermination = isTerminationSpecComplete || isTerminationInfoComplete, + noTerminationSpec = noTerminationSpec && noTerminationInfo + ) } + + def specifiesTermination(m: Method): TerminationSpecState = specifiesTermination(m.pres ++ m.posts, m.info) + def specifiesTermination(w: While): TerminationSpecState = specifiesTermination(w.invs, w.info) } case class VarAnalysisGraphMap(prog: Program, @@ -77,7 +140,7 @@ case class VarAnalysisGraphMap(prog: Program, reportErrorWithMsg: AbstractError => Unit) { // Maps all influences for a given variable - private type GraphMap = Map[Declaration, Set[LocalVarDecl]] + private type GraphMap = Map[InfluenceSink, Set[InfluenceSource]] // Storage for method analysis private val methodAnalysisMap: mutable.Map[Method, GraphMap] = mutable.Map() @@ -85,28 +148,32 @@ case class VarAnalysisGraphMap(prog: Program, def checkUserProvidedInfluencesSpec(): Unit = { - prog.methods.filter(m => m.posts.collect({ case i: FlowAnnotation => i }).nonEmpty).foreach( - method => executeTaintedGraphMethodAnalysis(method) - ) + val methodsWithFlowAnnotations = prog.methods.filter(_.posts.exists { + case _: FlowAnnotation => true + case _ => false + }) + methodsWithFlowAnnotations.foreach(executeTaintedGraphMethodAnalysis) } /** execute the information flow analysis with graphs. * When executed on the universal introduction statement the tainted variables are simply the quantified variables */ - def executeTaintedGraphAnalysis(allVars: Set[LocalVarDecl], tainted: Set[LocalVarDecl], blk: Seqn, volatileVars: Set[LocalVarDecl], u: UniversalIntro): Unit = { + def executeTaintedGraphAnalysis(allVars: Set[LocalVarDecl], tainted: Set[LocalVarDecl], blk: Seqn, volatileSinks: Set[NonAssumeInfluenceSink], u: UniversalIntro): Unit = { // Build initial graph where every variable and the heap influences itself - val initialGraph = allVars.map(k => k.asInstanceOf[Declaration] -> Set(k)).toMap + (AnalysisUtils.heapVertex -> Set(AnalysisUtils.heapVertex)) + val initialGraph: GraphMap = allVars.map(k => getSinkFromVarDecl(k) -> Set[InfluenceSource](LocalVarSource(k.localVar))).toMap + (AnalysisUtils.HeapSink -> Set[InfluenceSource](AnalysisUtils.HeapSource)) val graph = computeInfluenceMap(blk, initialGraph) - val problems = volatileVars.filter(v => graph(v).intersect(tainted).nonEmpty) - - if(problems.nonEmpty) { - reportErrorWithMsg(ConsistencyError("Universal introduction variable might have been assigned to variable " + problems + ", used in the quantified implication", u.pos)) + val taintedSources: Set[InfluenceSource] = tainted.map(decl => LocalVarSource(decl.localVar)) + val taintedNonAssumeSinks = volatileSinks.filter(v => graph(v).intersect(taintedSources).nonEmpty) + taintedNonAssumeSinks.foreach { + case AnalysisUtils.HeapSink => reportErrorWithMsg(ConsistencyError("Universally introduced variables might influence the heap", u.pos)) + case LocalVarSink(v) => reportErrorWithMsg(ConsistencyError(s"Universally introduced variables might influence variable $v", u.pos)) } - val assumeProblems = graph.keySet.filter(v => v.isInstanceOf[AssumeNode] && graph(v).intersect(tainted).nonEmpty) - assumeProblems.foreach { - case AssumeMethodNode(p) => reportErrorWithMsg(ConsistencyError(s"Universal introduction variable might influence the method's termination or assume statements in the method's body", p)) - case AssumeStmtNode(p) => reportErrorWithMsg(ConsistencyError(s"Universal introduction variable might influence assume statement", p)) - case AssumeWhileNode(p) => reportErrorWithMsg(ConsistencyError(s"Universal introduction variable might influence loop termination", p)) + val assumeSinks = graph.keySet.collect { case s: AssumeInfluenceSink => s } + val taintedAssumes = assumeSinks.filter(assumeSink => graph(assumeSink).intersect(taintedSources).nonEmpty) + taintedAssumes.foreach { + case AssumeMethodInfluenceSink(p) => reportErrorWithMsg(ConsistencyError(s"Universal introduction variable might influence the method's termination or assume statements in the method's body", p.pos)) + case AssumeStmtInfluenceSink(p) => reportErrorWithMsg(ConsistencyError(s"Universal introduction variable might influence assume statement", p.pos)) + case AssumeWhileInfluenceSink(p) => reportErrorWithMsg(ConsistencyError(s"Universal introduction variable might influence loop termination", p.pos)) } } @@ -118,180 +185,211 @@ case class VarAnalysisGraphMap(prog: Program, * and an over approximation for the assume analysis */ def executeTaintedGraphMethodAnalysis(method: Method): Unit = { - if (method.body.isEmpty) { - // Case for abstract methods - val map = getDefaultMethodInfluences(method) - methodAnalysisMap.put(method, map) - } else if(methodAnalysisStarted.contains(method)) { + if (methodAnalysisStarted.contains(method)) { // Case for recursive methods - if(!methodReturnInfluencesFullySpecified(method)) { - logger.warn(s"Taint analysis does not support recursive method calls. Falling back to specified influences. (${method.name} ${method.pos})") + if (!methodReturnInfluencesFullySpecified(method)) { + logger.info(s"Taint analysis encountered an incomplete flow annotation and conservatively over-approximates information flows. (${method.name} ${method.pos})") } - methodAnalysisMap.put(method, getDefaultMethodInfluences(method)) + methodAnalysisMap.put(method, getGraphForRecursiveOrAbstractMethod(method)) } else { - // Default case - methodAnalysisStarted.addOne(method) - val initialGraph: GraphMap = (method.formalArgs.map(k => k.asInstanceOf[Declaration] -> Set(k)) ++ method.formalReturns.map(k => k -> Set[LocalVarDecl]())).toMap + (AnalysisUtils.heapVertex -> Set(AnalysisUtils.heapVertex)) - - val stmt = Seqn(method.body.get.ss, method.body.get.scopedSeqnDeclarations.filter({ - case l: LocalVarDecl => !initialGraph.contains(l) - case _ => true - }))(method.body.get.pos, method.body.get.info, method.body.get.errT) + method.body match { + case None => + // Case for abstract methods + val map = getGraphForRecursiveOrAbstractMethod(method) + methodAnalysisMap.put(method, map) + case Some(seqn) => + // Default case + methodAnalysisStarted.addOne(method) + val initialGraph: GraphMap = + method.formalArgs.map(k => getSinkFromVarDecl(k) -> Set(getSourceFromVarDecl(k))).toMap ++ + method.formalReturns.map(k => getSinkFromVarDecl(k) -> Set.empty[InfluenceSource]).toMap + + (HeapSink -> Set(HeapSource)) + + val stmt = seqn.copy(scopedSeqnDeclarations = seqn.scopedSeqnDeclarations.filter({ + case l: LocalVarDecl => !initialGraph.contains(LocalVarSink(l.localVar)) + case _ => true + }))(seqn.pos, seqn.info, seqn.errT) + + var map = computeInfluenceMap(stmt, initialGraph) + val assumeVars = map.keySet.collect { + case v: AssumeInfluenceSink => v + } - var map = computeInfluenceMap(stmt, initialGraph) - val assumeVars = map.keySet.filter(v => v.isInstanceOf[AssumeNode]) + // Remove all assume nodes and save them as a single assume node for the whole method + if (assumeVars.nonEmpty) { + map = map.removedAll(assumeVars) + (AssumeMethodInfluenceSink(method) -> assumeVars.flatMap(v => map(v))) - // Remove all assume nodes and save them as a single assume node for the whole method - if(assumeVars.nonEmpty) { - map = map.removedAll(assumeVars) + (AssumeMethodNode(method.pos) -> assumeVars.flatMap(v => map(v))) - } - - // Check calculated value against the provided specification if there are any - method.posts.collect({ case f: FlowAnnotation => f }).foreach(f => { - val returnVar = AnalysisUtils.getDeclarationFromFlowVar(f.v, method) - val specifiedInfluences = f.varList.map(v => AnalysisUtils.getLocalVarDeclFromFlowVar(v)).toSet - val calculatedInfluences = lookupVar(returnVar, map) + // we enter this branch if there are any assume statements in this method's or any callees' body + // check that user-provided spec did not specify that there are none + val assumesNothings = method.posts.collect { case n: AssumesNothing => n } + assumesNothings.foreach { + n => reportErrorWithMsg(ConsistencyError(s"Contradicting flow annotation: Method might assume or inhale, which is caused by ${assumeVars.map(_.pretty).mkString(", ")}", n.pos)) + } + } - if (!calculatedInfluences.subsetOf(specifiedInfluences)) { - reportErrorWithMsg(ConsistencyError(s"Specified influence on return variable $returnVar is missing some potential influences. Specified: $specifiedInfluences Calculated: $calculatedInfluences", f.pos)) - } + // Check calculated value against the provided influencedBy annotations if there are any + val influencedBys = method.posts.collect { case f: InfluencedBy => f } + influencedBys.foreach(f => { + val returnVar = AnalysisUtils.getSinkFromFlowVar(f.v, method) + val specifiedInfluences = f.varList.map(v => AnalysisUtils.getSourceFromFlowVar(v)).toSet + val calculatedInfluences = lookupVar(returnVar, map) - if (calculatedInfluences.intersect(specifiedInfluences).size < calculatedInfluences.size) { - logger.warn(s"Specified influence on return variable $returnVar potentially assumes too many influences. Specified: $specifiedInfluences Calculated: $calculatedInfluences, (${f.pos})") - } + if (!calculatedInfluences.subsetOf(specifiedInfluences)) { + reportErrorWithMsg(ConsistencyError(s"Specified influence on return variable $returnVar is missing some potential influences. Specified: $specifiedInfluences Calculated: $calculatedInfluences", f.pos)) + } - val noAssumesSpecified = method.pres.concat(method.posts).collect({ case _: NoAssumeAnnotation => true }).nonEmpty - if (noAssumesSpecified && map.contains(AssumeMethodNode(method.pos))) { - reportErrorWithMsg(ConsistencyError(s"Method with assumesNothing specification might perform an assume or inhale", f.pos)) - } + if (calculatedInfluences.intersect(specifiedInfluences).size < calculatedInfluences.size) { + logger.warn(s"Specified influence on return variable $returnVar potentially assumes too many influences. Specified: $specifiedInfluences Calculated: $calculatedInfluences, (${f.pos})") + } - map = map + (returnVar -> specifiedInfluences) - }) - methodAnalysisMap.put(method, map) + map = map + (returnVar -> specifiedInfluences) + }) + methodAnalysisMap.put(method, map) + } } + methodAnalysisStarted -= method } private def methodReturnInfluencesFullySpecified(method: Method): Boolean = { - val allVars = method.posts.collect({ case f: FlowAnnotation => f.v }) + val allVars = method.posts.collect({ case f: InfluencedBy => f.v }) val vars = allVars.collect({ case Var(localVar) => getLocalVarDeclFromLocalVar(localVar)}) - method.formalReturns.map(v => vars.contains(v)).forall(b => b) && allVars.exists(v => v.isInstanceOf[Heap]) && allVars.exists(v => v.isInstanceOf[Assumes]) + method.formalReturns.forall(v => vars.contains(v)) && allVars.exists(v => v.isInstanceOf[Heap]) && allVars.exists(v => v.isInstanceOf[Assumes]) } /** - * Fallback for the influence analysis of methods. If influences for a return variable (including the heap) are declared, they are trusted. - * Otherwise we over approximate by assuming every method arg (including the heap) influences the return variable. - */ - private def getDefaultMethodInfluences(method: Method): GraphMap = { + * Returns the graph map for invocations of a recursive or abstract method. + * In the case of a recursive method, the returned graph map is used as a summary of the recursive call and the caller's + * analysis continues. The returned graph contains all user-provided influenced-by annotations, which means that the + * caller's analysis checks that these annotations form a (over-approximating) fixed point. All return variables (and + * the heap) for which the user did _not_ provide influenced-by annotations, we over-approximate and treat them as + * being influenced by all input parameters and the heap. + * While the graph for an abstract method is computed in the very same way, the user-provided annotations is effectively + * trusted because we cannot use the method's body to check that these annotations represent a valid fixed point. + * AssumeNothing and "influence assume by" annotations are treated in a similar way, i.e., are included in the returned + * graph map if specified. If both of these annotations are missing, we over-approximate for possibly non-terminating + * methods, i.e., the returned graph map specifies that assumes are (potentially) influenced by all input parameters + * and the heap. If a method is guaranteed to terminate (according to its termination measures), we treat the method + * as if `AssumeNothing` has been specified. + */ + private def getGraphForRecursiveOrAbstractMethod(method: Method): GraphMap = { // We ignore the assume vertex on purpose here, as a missing assume vertex is treated as if no assume statement appears in the method - val retSet = method.formalReturns.toSet.asInstanceOf[Set[Declaration]] + AnalysisUtils.heapVertex - val allMethodArgsSet = method.formalArgs.toSet + AnalysisUtils.heapVertex + val retSet: Set[InfluenceSink] = method.formalReturns.map(decl => LocalVarSink(decl.localVar)).toSet + HeapSink + val allMethodArgsSet: Set[InfluenceSource] = method.formalArgs.map(decl => LocalVarSource(decl.localVar)).toSet + HeapSource - val annotationInfluences = method.posts.collect({ case FlowAnnotation(returned, arguments) => (returned, arguments) }).map(t => { - AnalysisUtils.getDeclarationFromFlowVar(t._1, method) -> t._2.map(f => AnalysisUtils.getLocalVarDeclFromFlowVar(f)).toSet + val annotatedInfluencedBys = method.posts.collect({ case a: InfluencedBy => a }).map(a => { + AnalysisUtils.getSinkFromFlowVar(a.v, method) -> a.varList.map(f => AnalysisUtils.getSourceFromFlowVar(f)).toSet }).toMap /** influence all return variables, not mentioned by annotation, by every method argument */ - val otherInfluences = (retSet -- annotationInfluences.keySet).map(retVar => { + val unannotatedInfluencedBys = (retSet -- annotatedInfluencedBys.keySet).map(retVar => { retVar -> allMethodArgsSet - }) - val (containsDecreases, containsDecreasesStar) = AnalysisUtils.specifiesTermination(method) - val terminates = containsDecreases && !containsDecreasesStar - val noAssumes = method.pres.concat(method.posts).collect({case _: NoAssumeAnnotation => true}).nonEmpty + }).toMap + val terminates = AnalysisUtils.specifiesTermination(method).guaranteesTermination + val noAssumes = method.posts.exists { + case _: AssumesNothing => true + case _ => false + } - if(annotationInfluences.contains(AssumeMethodNode(method.pos)) || terminates || noAssumes) { - annotationInfluences ++ otherInfluences + // add information about assume statements. We distinguish two cases: (1) Either the user specified which variables + // influence an assume, that no assume statement occurs or the method terminates and (2) otherwise. + // In the first case, we do not have to add any influenced by annotations and in the second case, we over-approximate and + // add an annotation stating that all input parameters and the heap possibly influence a possibly existing assume statements. + if(annotatedInfluencedBys.contains(AssumeMethodInfluenceSink(method)) || terminates || noAssumes) { + annotatedInfluencedBys ++ unannotatedInfluencedBys } else { - annotationInfluences ++ otherInfluences + (AssumeMethodNode(method.pos) -> allMethodArgsSet) + val assumeInfluence: GraphMap = Map(AssumeMethodInfluenceSink(method) -> allMethodArgsSet) + annotatedInfluencedBys ++ unannotatedInfluencedBys ++ assumeInfluence } } private def computeInfluenceMap(stmt: Stmt, graphMap: GraphMap): GraphMap = { + /** extends heap influences by all sources of `exp` and returns the updated graph */ + def addExpSourcesToHeap(graphMap: GraphMap, exp: Exp): GraphMap = { + val vars = getResolvedVarsFromExp(exp, graphMap) + graphMap + (HeapSink -> (lookupVar(HeapSink, graphMap) ++ vars)) + } + stmt match { case s: Scope => // Temporarily add the scoped declarations to the graph and remove them afterwards val declarations = s.scopedDecls.collect({ case l: LocalVarDecl => l }) - val scopedGraph = graphMap ++ declarations.map(decl => decl -> Set(decl)).toMap - val graph = s match { + val scopedGraph = graphMap ++ declarations.map(decl => LocalVarSink(decl.localVar) -> Set[InfluenceSource](LocalVarSource(decl.localVar))).toMap + val graph: GraphMap = s match { case Seqn(ss, _) => ss.foldLeft(scopedGraph) { (graph, subStmt) => computeInfluenceMap(subStmt, graph) } + case u: UniversalIntro => computeInfluenceMap(u.block, scopedGraph) case o: OldCall => val met = prog.findMethod(o.methodName) - computeMethodInfluenceMap(graphMap, met, o.args, o.rets, o.pos) + computeMethodInfluenceMap(graphMap, met, o.args, o.rets) // The quantified variables of the Quasihavocall statement are ignored because they are untainted by definition case Quasihavocall(_, lhs, _) => - val vars = lhs.map(e => getResolvedVarsFromExp(e, graphMap)).getOrElse(Set()) - graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars)) - case u: UniversalIntro => computeInfluenceMap(u.block, scopedGraph) + val vars = lhs.map(e => getResolvedVarsFromExp(e, graphMap)).getOrElse(Set.empty) + graphMap + (HeapSink -> (lookupVar(HeapSink, graphMap) ++ vars)) } - graph.removedAll(declarations) + graph.removedAll(declarations.map(decl => LocalVarSink(decl.localVar))) case LocalVarAssign(lhs, rhs) => - val influences = getResolvedVarsFromExp(rhs, graphMap).flatMap(v => lookupVar(v, graphMap)) - graphMap + (AnalysisUtils.getLocalVarDeclFromLocalVar(lhs) -> influences) + val influences = getResolvedVarsFromExp(rhs, graphMap) + graphMap + (LocalVarSink(lhs) -> influences) case If(cond, thn, els) => val conditionVars = getResolvedVarsFromExp(cond, graphMap) - // For the condition influences, we only care for variables that are declared outside of the if block - val writesInIfBlocks = (getModifiedVars(thn) ++ getModifiedVars(els)).filter(v => v.isInstanceOf[AssumeNode] || graphMap.contains(v)) - val conditionInfluences = writesInIfBlocks.map(v => v -> (lookupVar(v, graphMap) ++ conditionVars.flatMap(c => lookupVar(c, graphMap)))).toMap + // For the condition influences, we only care for variables that are declared outside of the if block and for assume annotations + val writesInIfBlocks = (getModifiedVars(thn) ++ getModifiedVars(els)).filter(v => AnalysisUtils.isAssumeSink(v) || graphMap.contains(v)) + val conditionInfluences = writesInIfBlocks.map(v => v -> (lookupVar(v, graphMap) ++ conditionVars)).toMap val thenGraph = computeInfluenceMap(thn, graphMap) val elseGraph = computeInfluenceMap(els, graphMap) (thenGraph.keySet ++ elseGraph.keySet).map( - v => v -> (thenGraph.getOrElse(v, Set()) ++ elseGraph.getOrElse(v, Set()) ++ conditionInfluences.getOrElse(v, Set())) + v => v -> (thenGraph.getOrElse(v, Set.empty) ++ elseGraph.getOrElse(v, Set.empty) ++ conditionInfluences.getOrElse(v, Set.empty)) ).toMap - case While(cond, invs, body) => - var iterationGraph = computeInfluenceMap(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), graphMap) + case s@ While(cond, _, body) => + val unrolledLoop = If(cond, body, Seqn(Seq.empty, Seq.empty)())(body.pos, body.info, body.errT) var edgesEqual: Boolean = false - var mergeGraph = iterationGraph + var mergedGraph = computeInfluenceMap(unrolledLoop, graphMap) while (!edgesEqual) { - iterationGraph = computeInfluenceMap(If(cond, body, Seqn(Seq(), Seq())(body.pos))(body.pos), mergeGraph) - if(iterationGraph.equals(mergeGraph)) { + val iterationGraph = computeInfluenceMap(unrolledLoop, mergedGraph) + if(iterationGraph.equals(mergedGraph)) { edgesEqual = true } else { - mergeGraph = iterationGraph + mergedGraph = iterationGraph } } - val loopTerminates = invs.collect { case DecreasesTuple(_, _) | DecreasesWildcard(_) => true }.nonEmpty && invs.collect { case DecreasesStar() => true }.isEmpty + val loopTerminates = AnalysisUtils.specifiesTermination(s).guaranteesTermination if(loopTerminates) { - mergeGraph + mergedGraph } else { - mergeGraph + (AssumeWhileNode(cond.pos) -> getResolvedVarsFromExp(cond, mergeGraph)) + mergedGraph + (AssumeWhileInfluenceSink(s) -> getResolvedVarsFromExp(cond, mergedGraph)) } case m: MethodCall => val met = prog.findMethod(m.methodName) - computeMethodInfluenceMap(graphMap, met, m.args, m.targets, m.pos) + computeMethodInfluenceMap(graphMap, met, m.args, m.targets) case FieldAssign(_, rhs) => - val vars = getResolvedVarsFromExp(rhs, graphMap) - graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars.filter(v => !v.equals(AnalysisUtils.heapVertex)).flatMap(v => graphMap(v)))) + // after the assignment, the heap is influenced by everything that influences `rhs` (i.e. `vars`) and all things + // that influenced the heap before the assignment + addExpSourcesToHeap(graphMap, rhs) case Exhale(exp) => if (exp.isPure) { graphMap } else { - val vars = getResolvedVarsFromExp(exp, graphMap) - graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars)) + addExpSourcesToHeap(graphMap, exp) } case Fold(acc) => - val vars = getResolvedVarsFromExp(acc, graphMap) - graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars)) + addExpSourcesToHeap(graphMap, acc) case Unfold(acc) => - val vars = getResolvedVarsFromExp(acc, graphMap) - graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars)) + addExpSourcesToHeap(graphMap, acc) case Apply(exp) => - val vars = getResolvedVarsFromExp(exp, graphMap) - graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars)) + addExpSourcesToHeap(graphMap, exp) case Package(wand, _) => - val vars = getResolvedVarsFromExp(wand, graphMap) - graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars)) + addExpSourcesToHeap(graphMap, wand) case Quasihavoc(lhs, _) => - val vars = lhs.map(e => getResolvedVarsFromExp(e, graphMap)).getOrElse(Set()) - graphMap + (AnalysisUtils.heapVertex -> (lookupVar(AnalysisUtils.heapVertex, graphMap) ++ vars)) + lhs.map(exp => addExpSourcesToHeap(graphMap, exp)).getOrElse(graphMap) case Assert(_) => graphMap case Label(_, _) => graphMap // Assume analysis case a: Inhale => - val assumeVars = (lookupVar(AssumeStmtNode(a.pos), graphMap) ++ getResolvedVarsFromExp(a.exp, graphMap)).flatMap(v => lookupVar(v, graphMap) + v) - graphMap + (AssumeStmtNode(a.pos) -> assumeVars) - case ExistentialElim(vars, _, exp) => graphMap ++ vars.map(v => v -> getResolvedVarsFromExp(exp, graphMap)) + val assumeVars = lookupVar(AssumeStmtInfluenceSink(a), graphMap) ++ getResolvedVarsFromExp(a.exp, graphMap) + graphMap + (AssumeStmtInfluenceSink(a) -> assumeVars) + case ExistentialElim(vars, _, exp) => graphMap ++ vars.map(v => LocalVarSink(v.localVar) -> getResolvedVarsFromExp(exp, graphMap)) // Non handled cases case a: Assume => reportErrorWithMsg(ConsistencyError("Unexpected assume statement in the modular information flow analysis", a.pos)) @@ -307,21 +405,29 @@ case class VarAnalysisGraphMap(prog: Program, /** creates graph for method call and old call. maps expressions passed to methods to the method arguments, computes the graph based on the flow annotation, * and finally maps the return variables to the variables that the method is assigned to. */ - private def computeMethodInfluenceMap(graphMap: GraphMap, method: Method, callArgs: Seq[Exp], callTargets: Seq[LocalVar], pos: Position): GraphMap = { - /** set of all target variables that have not been included in the influenced by expression up until now */ - val methodArgExpMapping = (method.formalArgs zip callArgs).map(methodArg => - methodArg._1.asInstanceOf[Declaration] -> getResolvedVarsFromExp(methodArg._2, graphMap) - ).toMap + (AnalysisUtils.heapVertex -> Set(AnalysisUtils.heapVertex)) - - val retVarMapping = (callTargets.map(l => AnalysisUtils.getLocalVarDeclFromLocalVar(l)) zip method.formalReturns) - .map(vars => vars._2.asInstanceOf[Declaration] -> vars._1.asInstanceOf[Declaration]).toMap + (AnalysisUtils.heapVertex -> AnalysisUtils.heapVertex) + (AssumeMethodNode(method.pos) -> AssumeMethodNode(pos)) + private def computeMethodInfluenceMap(graphMap: GraphMap, method: Method, callArgs: Seq[Exp], callTargets: Seq[LocalVar]): GraphMap = { + /** maps formal arguments to the influence sources of the corresponding call arguments */ + val methodArgExpMapping: Map[InfluenceSource, Set[InfluenceSource]] = (method.formalArgs zip callArgs).map { + case (formalArg, callArg) => + getSourceFromVarDecl(formalArg) -> getResolvedVarsFromExp(callArg, graphMap) + }.toMap + (HeapSource -> lookupVar(HeapSink, graphMap)) + + /** maps formal returns, heap, and assume sink to call targets, heap, and assume */ + val retVarMapping: Map[InfluenceSink, InfluenceSink] = + (method.formalReturns zip callTargets).map { + case (formalRet, callTarget) => getSinkFromVarDecl(formalRet) -> LocalVarSink(callTarget) + }.toMap + + (HeapSink -> HeapSink) + + // we set the position to the method call instead of the assume statement, so potential error are more readable: + (AssumeMethodInfluenceSink(method) -> AssumeMethodInfluenceSink(method)) val resolvedMethodMap = getMethodAnalysisMap(method) .filter(v => retVarMapping.contains(v._1)) - .map(v => retVarMapping(v._1) -> v._2.flatMap(methodArgExpMapping)) + - (AnalysisUtils.heapVertex -> (graphMap(AnalysisUtils.heapVertex) ++ methodAnalysisMap(method)(AnalysisUtils.heapVertex).flatMap(methodArgExpMapping))) + .map { + // extend the method analysis graph by transitivity via the method's arguments: + case (sink, sources) => retVarMapping(sink) -> sources.flatMap(methodArgExpMapping) + } - // We set the position to the method call instead of the assume statement, so potential error are more readable. graphMap ++ resolvedMethodMap } @@ -329,72 +435,72 @@ case class VarAnalysisGraphMap(prog: Program, if (!methodAnalysisMap.contains(method)) { executeTaintedGraphMethodAnalysis(method) } - methodAnalysisMap(method) } /** * get the variables that were modified by the statement stmt */ - def getModifiedVars(stmt: Stmt): Set[Declaration] = { + def getModifiedVars(stmt: Stmt): Set[InfluenceSink] = { stmt match { case Seqn(ss, _) => ss.flatMap(s => getModifiedVars(s)).toSet - case LocalVarAssign(l, _) => Set(getLocalVarDeclFromLocalVar(l)) + case LocalVarAssign(l, _) => Set(LocalVarSink(l)) case If(_, thn, els) => getModifiedVars(thn) ++ getModifiedVars(els) case While(_, _, body) => getModifiedVars(body) case MethodCall(name, _, targets) => - val methodInfluences = getMethodAnalysisMap(prog.methodsByName(name)) - if(methodInfluences.exists(v => v._1.isInstanceOf[AssumeNode])) { - targets.map(getLocalVarDeclFromLocalVar).toSet + AssumeMethodNode(stmt.pos) + val method = prog.methodsByName(name) + val methodInfluences = getMethodAnalysisMap(method) + val mightContainAssumeStmts = methodInfluences.keySet.exists(AnalysisUtils.isAssumeSink) + val targetSinks: Set[InfluenceSink] = targets.map(target => LocalVarSink(target)).toSet + if (mightContainAssumeStmts) { + targetSinks + AssumeMethodInfluenceSink(method) } else { - targets.map(getLocalVarDeclFromLocalVar).toSet + targetSinks } - case i: Inhale => Set(AssumeStmtNode(i.pos)) - case a: Assume => Set(AssumeStmtNode(a.pos)) - case Label(_, _) => Set() - case Quasihavoc(_, _) => Set(heapVertex) - case Quasihavocall(_, _, _) => Set(heapVertex) - case ExistentialElim(vars, _, _) => vars.toSet - case _ => Set() + case i: Inhale => Set(AssumeStmtInfluenceSink(i)) + case _: Assume => + assert(assertion = false, "Assume statements should have been replaced by inhale statements") + ??? + case Label(_, _) => Set.empty + case Quasihavoc(_, _) => Set(HeapSink) + case Quasihavocall(_, _, _) => Set(HeapSink) + case ExistentialElim(vars, _, _) => vars.map(v => LocalVarSink(v.localVar)).toSet + case _ => Set.empty } } - private def lookupVar(variable: Declaration, graphMap: GraphMap): Set[LocalVarDecl] = { + private def lookupVar(variable: InfluenceSink, graphMap: GraphMap): Set[InfluenceSource] = { // Assume Nodes are added when they are first encountered(can be a method call or a assume / inhale statement), so they might not exist when looked up - if(variable.isInstanceOf[AssumeNode]) { - graphMap.getOrElse(variable, Set()) - } else { - assert(graphMap.contains(variable), "Variable " + variable + " not found in graph analysis") - graphMap(variable) + variable match { + case _: AssumeInfluenceSink => graphMap.getOrElse(variable, Set.empty) + case _ => + assert(graphMap.contains(variable), "Variable " + variable + " not found in graph analysis") + graphMap(variable) } } /** * Gets all variables used in a given expressions and maps them to their influences specified in given graph map */ - private def getResolvedVarsFromExp(exp: Exp, graphMap: GraphMap): Set[LocalVarDecl] = { - getLocalVarDeclsFromExpr(exp).flatMap(v => lookupVar(v, graphMap)) + private def getResolvedVarsFromExp(exp: Exp, graphMap: GraphMap): Set[InfluenceSource] = { + getSinksFromExpr(exp).flatMap(v => lookupVar(v, graphMap)) } - /** - * returns all the variables inside an expression - * - * @param exp expressions from which all variables should be returned - * @return set of Variable declarations - */ - def getLocalVarDeclsFromExpr(exp: Exp): Set[LocalVarDecl] = { + /** returns all the sinks inside an expression */ + def getSinksFromExpr(exp: Exp): Set[NonAssumeInfluenceSink] = { exp match { - case l@LocalVar(_, _) => Set(getLocalVarDeclFromLocalVar(l)) - case BinExp(exp1, exp2) => getLocalVarDeclsFromExpr(exp1) ++ getLocalVarDeclsFromExpr(exp2) - case UnExp(exp) => getLocalVarDeclsFromExpr(exp) + case l: LocalVar => Set(LocalVarSink(l)) + case BinExp(exp1, exp2) => getSinksFromExpr(exp1) ++ getSinksFromExpr(exp2) + case UnExp(exp) => getSinksFromExpr(exp) case f: FuncApp => - val heapDependent = Expressions.couldBeHeapDependent(f.func(prog), prog) - f.args.flatMap(e => getLocalVarDeclsFromExpr(e).filter(v => v.typ != Ref)).toSet ++ (if (heapDependent) Set(heapVertex) else Set()) - case DomainFuncApp(_, exps, _) => exps.flatMap(e => getLocalVarDeclsFromExpr(e)).toSet - case _: ForPerm | _: CurrentPerm => Set(heapVertex) - case FieldAccess(v, _) => getLocalVarDeclsFromExpr(v) + heapVertex - case AccessPredicate(access, _) => getLocalVarDeclsFromExpr(access) + heapVertex - case _ => Set() + val isHeapDependent = Expressions.couldBeHeapDependent(f.func(prog), prog) + // TODO: do we need to filter out Refs? + f.args.flatMap(e => getSinksFromExpr(e)).toSet ++ (if (isHeapDependent) Set(HeapSink) else Set.empty) + case DomainFuncApp(_, exps, _) => exps.flatMap(e => getSinksFromExpr(e)).toSet + case _: ForPerm | _: CurrentPerm => Set(HeapSink) + case FieldAccess(v, _) => getSinksFromExpr(v) + HeapSink + case AccessPredicate(access, _) => getSinksFromExpr(access) + HeapSink + case _ => Set.empty } } } diff --git a/src/test/resources/reasoning/existential_elim_fail1.vpr b/src/test/resources/reasoning/existential_elim_fail1.vpr new file mode 100644 index 000000000..8811d6472 --- /dev/null +++ b/src/test/resources/reasoning/existential_elim_fail1.vpr @@ -0,0 +1,11 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + + +field f: Int + +method ex2() +{ + //:: ExpectedOutput(typechecker.error) + obtain x: Ref where {x.f} acc(x.f) // impure expressions are disallowed +} diff --git a/src/test/resources/reasoning/existential_elim.vpr b/src/test/resources/reasoning/existential_elim_success1.vpr similarity index 75% rename from src/test/resources/reasoning/existential_elim.vpr rename to src/test/resources/reasoning/existential_elim_success1.vpr index 25e2ddeae..4e54d8ee0 100644 --- a/src/test/resources/reasoning/existential_elim.vpr +++ b/src/test/resources/reasoning/existential_elim_success1.vpr @@ -10,15 +10,8 @@ method ex1() { obtain x:Int where eq(x, 42) assert x == 42 -} - - -field f: Int - -method ex2() -{ - //:: ExpectedOutput(typechecker.error) - obtain x: Ref where {x.f} acc(x.f) + //:: ExpectedOutput(assert.failed:assertion.false) + assert false } function geq(x:Int, y:Int) : Bool @@ -31,4 +24,6 @@ method ex3() assert geq(3, 0) obtain x:Int, y:Int where {geq(x,y)} geq(x,y) assert x>=y + //:: ExpectedOutput(assert.failed:assertion.false) + assert false } diff --git a/src/test/resources/reasoning/old_call.vpr b/src/test/resources/reasoning/old_call_fail1.vpr similarity index 73% rename from src/test/resources/reasoning/old_call.vpr rename to src/test/resources/reasoning/old_call_fail1.vpr index 7ce7389d2..69c314d6f 100644 --- a/src/test/resources/reasoning/old_call.vpr +++ b/src/test/resources/reasoning/old_call_fail1.vpr @@ -6,7 +6,9 @@ method foo() requires oldCall[l](lemma()) { var x: Int - x := oldCall[l](lemma()) + //:: ExpectedOutput(typechecker.error) + x := oldCall[l](lemma()) // label l does not yet exist + label l //:: ExpectedOutput(typechecker.error) x := 42 + oldCall[l](lemma()) } diff --git a/src/test/resources/reasoning/old_call_fail2.vpr b/src/test/resources/reasoning/old_call_fail2.vpr new file mode 100644 index 000000000..51ddd1389 --- /dev/null +++ b/src/test/resources/reasoning/old_call_fail2.vpr @@ -0,0 +1,12 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +method foo() +{ + var x: Int + label l + //:: ExpectedOutput(typechecker.error) + x := oldCall[l](nonlemma()) // only lemmas can be called +} + +method nonlemma() returns (x: Int) diff --git a/src/test/resources/reasoning/old_call_success1.vpr b/src/test/resources/reasoning/old_call_success1.vpr new file mode 100644 index 000000000..79b37dba2 --- /dev/null +++ b/src/test/resources/reasoning/old_call_success1.vpr @@ -0,0 +1,23 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +method foo() +{ + label l + + oldCall[l](lemma(42)) + + var y: Int + y := lemma(42) + y := oldCall[l](lemma(42)) + + y := oldCall[l](lemma(42)) +} + +method foo(x: Int) returns (y: Int) +isLemma +// decreases // is necessary for lemma +ensures x == y +{ + y := x +} From 33e0b3393f042b5d6ab3558e5e9bcab9cae85506 Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Mon, 1 Jul 2024 12:11:13 +0200 Subject: [PATCH 38/41] fix a bug where local variables in methods were not correctly cleaned up & added test case --- .../analysis/VarAnalysisGraphMap.scala | 2 ++ .../universal_introduction/ui_methods.vpr | 29 ++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index 741701339..12b6b108e 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -167,6 +167,8 @@ case class VarAnalysisGraphMap(prog: Program, map = map + (returnVar -> specifiedInfluences) }) + // Remove actual local variables, so we only see method argument and heap influences + map = map.map(entry => entry._1 -> entry._2.filter(v => method.formalArgs.contains(v) || v == heapVertex)) methodAnalysisMap.put(method, map) } methodAnalysisStarted -= method diff --git a/src/test/resources/reasoning/universal_introduction/ui_methods.vpr b/src/test/resources/reasoning/universal_introduction/ui_methods.vpr index ff995f1a3..66195b0fa 100644 --- a/src/test/resources/reasoning/universal_introduction/ui_methods.vpr +++ b/src/test/resources/reasoning/universal_introduction/ui_methods.vpr @@ -267,23 +267,36 @@ method test9(x: Ref, y: Int) } -method testRec1(x: Int) +method testRec1(x: Ref) requires acc(x.f) - influenced heap by {heap} + influenced heap by {heap, x} { - if (y == 0) { + if (x.f == 0) { exhale acc(x.f) } } -method testRec2(x: Int) +method testRec2(x: Ref) requires acc(x.f) - influenced heap by {heap} + influenced heap by {heap, x} { - testAssumesAbstract1(x) + testAssumesAbstract(x) testRec1(x) } -method testAssumesAbstract(x: Int) - assumesUsing {x} \ No newline at end of file +method testAssumesAbstract(x: Ref) + influenced assumes by {x} + +function f1(i1: Int, i2: Int): Int + +method test_local_var_resolve(i2: Int) returns (ret: Int){ + var i1: Int + ret := f1(i1, i2) +} + +method test_local_var_resolve_caller() { + prove forall x: Int assuming x != 0 implies x == 1 { + var i: Int := test_local_var_resolve(x) + } +} \ No newline at end of file From 1d36454b8e8a1e2517b242275347fe91b5d8c968 Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Mon, 1 Jul 2024 15:33:30 +0200 Subject: [PATCH 39/41] remove local variables from calculated influences before they are checked & fix some error messages --- .../silver/plugin/standard/reasoning/BeforeVerifyHelper.scala | 4 ++-- .../standard/reasoning/analysis/VarAnalysisGraphMap.scala | 4 ++-- .../resources/reasoning/universal_introduction/ui_methods.vpr | 4 +++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala index 20469ea34..2616359b2 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/BeforeVerifyHelper.scala @@ -126,7 +126,7 @@ trait BeforeVerifyHelper { val targetVarDecl = AnalysisUtils.getDeclarationFromFlowVar(target, method) if (!retVars.contains(targetVarDecl)) { - reportError(ConsistencyError(s"Only return parameters, the heap or assumes can be influenced. ${targetVarDecl.name} is not be a return parameter.", v.pos)) + reportError(ConsistencyError(s"Only return parameters, the heap or assumes can be influenced. ${targetVarDecl.name} is none of these.", v.pos)) } if (seenVars.contains(targetVarDecl)) { reportError(ConsistencyError(s"Only one influenced by expression per return parameter can exist. ${targetVarDecl.name} is used several times.", v.pos)) @@ -136,7 +136,7 @@ trait BeforeVerifyHelper { args.foreach(arg => { val argVarDecl = AnalysisUtils.getLocalVarDeclFromFlowVar(arg) if (!argVars.contains(argVarDecl)) { - reportError(ConsistencyError(s"Only method input parameters or the heap can influence a return parameter. ${argVarDecl.name} is not be a method input parameter.", v.pos)) + reportError(ConsistencyError(s"Only method input parameters or the heap can influence a return parameter. ${argVarDecl.name} is none of these.", v.pos)) } }) case _ => diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index 12b6b108e..39b8387b2 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -146,6 +146,8 @@ case class VarAnalysisGraphMap(prog: Program, map = map.removedAll(assumeVars) + (AssumeMethodNode(method.pos) -> assumeVars.flatMap(v => map(v))) } + // Remove actual local variables, so we only see method argument and heap influences + map = map.map(entry => entry._1 -> entry._2.filter(v => method.formalArgs.contains(v) || v == heapVertex)) // Check calculated value against the provided specification if there are any method.posts.collect({ case f: FlowAnnotation => f }).foreach(f => { val returnVar = AnalysisUtils.getDeclarationFromFlowVar(f.v, method) @@ -167,8 +169,6 @@ case class VarAnalysisGraphMap(prog: Program, map = map + (returnVar -> specifiedInfluences) }) - // Remove actual local variables, so we only see method argument and heap influences - map = map.map(entry => entry._1 -> entry._2.filter(v => method.formalArgs.contains(v) || v == heapVertex)) methodAnalysisMap.put(method, map) } methodAnalysisStarted -= method diff --git a/src/test/resources/reasoning/universal_introduction/ui_methods.vpr b/src/test/resources/reasoning/universal_introduction/ui_methods.vpr index 66195b0fa..e81705444 100644 --- a/src/test/resources/reasoning/universal_introduction/ui_methods.vpr +++ b/src/test/resources/reasoning/universal_introduction/ui_methods.vpr @@ -290,7 +290,9 @@ method testAssumesAbstract(x: Ref) function f1(i1: Int, i2: Int): Int -method test_local_var_resolve(i2: Int) returns (ret: Int){ +method test_local_var_resolve(i2: Int) returns (ret: Int) +influenced ret by {i2} +{ var i1: Int ret := f1(i1, i2) } From 1e41b5ed31b138a284e5b6b8ee1fcbb4ee6fe15d Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Mon, 8 Jul 2024 13:24:51 +0200 Subject: [PATCH 40/41] remove TODO --- .../plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala index 694d08ef0..8f4766583 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/analysis/VarAnalysisGraphMap.scala @@ -503,7 +503,6 @@ case class VarAnalysisGraphMap(prog: Program, case UnExp(exp) => getSinksFromExpr(exp) case f: FuncApp => val isHeapDependent = Expressions.couldBeHeapDependent(f.func(prog), prog) - // TODO: do we need to filter out Refs? f.args.flatMap(e => getSinksFromExpr(e)).toSet ++ (if (isHeapDependent) Set(HeapSink) else Set.empty) case DomainFuncApp(_, exps, _) => exps.flatMap(e => getSinksFromExpr(e)).toSet case _: ForPerm | _: CurrentPerm => Set(HeapSink) From 8f2fa05a504c28a823725ac817241cf17a2c6329 Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Mon, 15 Jul 2024 15:28:29 +0200 Subject: [PATCH 41/41] add test, fix positioning & block placement --- src/main/scala/viper/silver/parser/ParseAst.scala | 2 +- .../standard/reasoning/ReasoningPASTExtension.scala | 6 +++--- .../plugin/standard/reasoning/ReasoningPlugin.scala | 4 ++-- .../reasoning/universal_introduction/ui_func.vpr | 11 +++++++++++ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main/scala/viper/silver/parser/ParseAst.scala b/src/main/scala/viper/silver/parser/ParseAst.scala index 539a80fa8..614548a5e 100644 --- a/src/main/scala/viper/silver/parser/ParseAst.scala +++ b/src/main/scala/viper/silver/parser/ParseAst.scala @@ -1467,7 +1467,7 @@ case class PNewExp(keyword: PKw.New, fields: PGrouped.Paren[Either[PSym.Star, PD def forceSubstitution(ts: PTypeSubstitution) = {} } -sealed trait PScope extends PNode { +trait PScope extends PNode { val scopeId = PScope.uniqueId() } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala index ae82d4df4..063f5a2e3 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPASTExtension.scala @@ -9,7 +9,7 @@ package viper.silver.plugin.standard.reasoning import viper.silver.FastMessaging import viper.silver.ast.{Exp, ExtensionExp, LocalVar, LocalVarDecl, Position, Seqn, Stmt, Trigger} import viper.silver.parser.TypeHelper.Bool -import viper.silver.parser.{NameAnalyser, PAssignTarget, PCall, PCallable, PDelimited, PExp, PExtender, PGrouped, PIdnRef, PIdnUseExp, PKeywordLang, PKeywordStmt, PKw, PLabel, PLocalVarDecl, PReserved, PSeqn, PStmt, PSym, PSymOp, PTrigger, PType, PTypeSubstitution, Translator, TypeChecker} +import viper.silver.parser.{NameAnalyser, PAssignTarget, PCall, PCallable, PDelimited, PExp, PExtender, PGrouped, PIdnRef, PIdnUseExp, PKeywordLang, PKeywordStmt, PKw, PLabel, PLocalVarDecl, PReserved, PScope, PSeqn, PStmt, PSym, PSymOp, PTrigger, PType, PTypeSubstitution, Translator, TypeChecker} case object PObtainKeyword extends PKw("obtain") with PKeywordLang with PKeywordStmt case object PWhereKeyword extends PKw("where") with PKeywordLang @@ -42,7 +42,7 @@ case class PExistentialElim(obtainKw: PReserved[PObtainKeyword.type], delimitedV } } -case class PUniversalIntro(proveKw: PReserved[PProveKeyword.type], forallKw: PKw.Forall, delimitedVarDecls: PDelimited[PLocalVarDecl, PSym.Comma], triggers: Seq[PTrigger], assumingKw: PReserved[PAssumingKeyword.type], e1: PExp, impliesKw: PReserved[PImpliesKeyword.type], e2: PExp, block: PSeqn)(val pos: (Position, Position)) extends PExtender with PStmt { +case class PUniversalIntro(proveKw: PReserved[PProveKeyword.type], forallKw: PKw.Forall, delimitedVarDecls: PDelimited[PLocalVarDecl, PSym.Comma], triggers: Seq[PTrigger], assumingKw: PReserved[PAssumingKeyword.type], e1: PExp, impliesKw: PReserved[PImpliesKeyword.type], e2: PExp, block: PSeqn)(val pos: (Position, Position)) extends PExtender with PStmt with PScope { lazy val varDecls: Seq[PLocalVarDecl] = delimitedVarDecls.toSeq override def typecheck(t: TypeChecker, n: NameAnalyser): Option[Seq[String]] = { @@ -60,7 +60,7 @@ case class PUniversalIntro(proveKw: PReserved[PProveKeyword.type], forallKw: PKw triggers.map { t1 => Trigger(t1.exp.inner.toSeq.map { t2 => t.exp(t2) })(t.liftPos(t1)) }, t.exp(e1), t.exp(e2), - t.stmt(block).asInstanceOf[Seqn])(t.liftPos(e1)) + t.stmt(block).asInstanceOf[Seqn])(t.liftPos(block)) } } diff --git a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala index 9e19a3bc6..ef5114531 100644 --- a/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala +++ b/src/main/scala/viper/silver/plugin/standard/reasoning/ReasoningPlugin.scala @@ -237,7 +237,7 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, Seq(Inhale(u.assumingExp)(u.assumingExp.pos)), Seq.empty)(u.assumingExp.pos), Seqn(Seq.empty, Seq.empty)(u.assumingExp.pos) - )(u.assumingExp.pos), + )(u.pos), // execute block u.block, // conditionally assert imply expression @@ -245,7 +245,7 @@ class ReasoningPlugin(@unused reporter: viper.silver.reporter.Reporter, Inhale(Forall(quantifiedVars, newTrigs, Implies(LabelledOld(newExp1, lbl)(u.implyingExp.pos), newExp2)(u.implyingExp.pos))(u.implyingExp.pos))(u.implyingExp.pos) ), Seq(boolvar) ++ u.varList - )(u.assumingExp.pos) + )(u.pos) } override def beforeVerify(input: Program): Program = { diff --git a/src/test/resources/reasoning/universal_introduction/ui_func.vpr b/src/test/resources/reasoning/universal_introduction/ui_func.vpr index 33cd76512..58ae73f0f 100644 --- a/src/test/resources/reasoning/universal_introduction/ui_func.vpr +++ b/src/test/resources/reasoning/universal_introduction/ui_func.vpr @@ -32,3 +32,14 @@ method mFunctionNOK() y := func1(x) } } + +function func2(e: Ref): Ref { + e +} + +method mFunction2NOK() { + var x: Ref + prove forall i:Ref assuming true implies i == x { + x := test(i) + } +} \ No newline at end of file