From 9c034cc7a32a798704513c7fdda03074c44c7ef0 Mon Sep 17 00:00:00 2001 From: Dina Weiersmueller Date: Wed, 9 Nov 2022 15:58:48 +0100 Subject: [PATCH 01/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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) }