From 495c82f86fb0826c8303db9035d21b2c9460c92a Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Thu, 9 Nov 2023 17:34:58 +0100 Subject: [PATCH 01/32] Start splitting up inference of SeqGuards and encoding of branch unanimity --- src/col/vct/col/ast/Node.scala | 2 +- ...ConditionImpl.scala => SeqGuardImpl.scala} | 6 +- .../vct/col/typerules/CoercingRewriter.scala | 2 +- src/main/vct/main/stages/Transformation.scala | 2 + .../veymont/AddVeyMontConditionNodes.scala | 108 ------------------ .../EncodeSequentialBranchUnanimity.scala | 83 ++++++++++++++ .../vct/rewrite/veymont/InferSeqGuards.scala | 77 +++++++++++++ .../veymont/ParalleliseVeyMontThreads.scala | 8 +- 8 files changed, 171 insertions(+), 117 deletions(-) rename src/col/vct/col/ast/expr/op/bool/{VeyMontConditionImpl.scala => SeqGuardImpl.scala} (51%) delete mode 100644 src/rewrite/vct/rewrite/veymont/AddVeyMontConditionNodes.scala create mode 100644 src/rewrite/vct/rewrite/veymont/EncodeSequentialBranchUnanimity.scala create mode 100644 src/rewrite/vct/rewrite/veymont/InferSeqGuards.scala diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index b6288af23e..5125655c84 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -550,7 +550,6 @@ final case class Star[G](left: Expr[G], right: Expr[G])(implicit val o: Origin) final case class Wand[G](left: Expr[G], right: Expr[G])(implicit val o: Origin) extends Expr[G] with WandImpl[G] final case class Scale[G](scale: Expr[G], res: Expr[G])(val blame: Blame[ScaleNegative])(implicit val o: Origin) extends Expr[G] with ScaleImpl[G] final case class ScaleByParBlock[G](block: Ref[G, ParBlockDecl[G]], res: Expr[G])(implicit val o: Origin) extends Expr[G] with ScaleByParBlockImpl[G] -final case class VeyMontCondition[G](condition: Seq[(Ref[G, Endpoint[G]], Expr[G])])(implicit val o: Origin) extends Expr[G] with VeyMontConditionImpl[G] final case class PolarityDependent[G](onInhale: Expr[G], onExhale: Expr[G])(implicit val o: Origin) extends Expr[G] with PolarityDependentImpl[G] final case class Unfolding[G](res: Expr[G], body: Expr[G])(val blame: Blame[UnfoldFailed])(implicit val o: Origin) extends Expr[G] with UnfoldingImpl[G] @@ -1273,6 +1272,7 @@ case class Access[G](subject: Subject[G], field: Ref[G, InstanceField[G]])(impli final case class Communicate[G](receiver: Access[G], sender: Access[G])(implicit val o: Origin) extends Statement[G] with CommunicateImpl[G] final case class SeqAssign[G](receiver: Ref[G, Endpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(implicit val o: Origin) extends Statement[G] with SeqAssignImpl[G] final case class EndpointUse[G](ref: Ref[G, Endpoint[G]])(implicit val o: Origin) extends Expr[G] with EndpointUseImpl[G] +final case class SeqGuard[G](conditions: Seq[(Ref[G, Endpoint[G]], Expr[G])])(implicit val o: Origin) extends Expr[G] with SeqGuardImpl[G] final case class VeyMontAssignExpression[G](endpoint: Ref[G, Endpoint[G]], assign: Statement[G])(implicit val o: Origin) extends Statement[G] with VeyMontAssignExpressionImpl[G] final case class CommunicateX[G](receiver: Ref[G, Endpoint[G]], sender: Ref[G, Endpoint[G]], chanType: Type[G], assign: Statement[G])(implicit val o: Origin) extends Statement[G] with CommunicateXImpl[G] diff --git a/src/col/vct/col/ast/expr/op/bool/VeyMontConditionImpl.scala b/src/col/vct/col/ast/expr/op/bool/SeqGuardImpl.scala similarity index 51% rename from src/col/vct/col/ast/expr/op/bool/VeyMontConditionImpl.scala rename to src/col/vct/col/ast/expr/op/bool/SeqGuardImpl.scala index 4821c0b9d2..f9df8e699c 100644 --- a/src/col/vct/col/ast/expr/op/bool/VeyMontConditionImpl.scala +++ b/src/col/vct/col/ast/expr/op/bool/SeqGuardImpl.scala @@ -1,12 +1,12 @@ package vct.col.ast.expr.op.bool -import vct.col.ast.{TBool, Type, VeyMontCondition} +import vct.col.ast.{TBool, Type, SeqGuard} import vct.col.print._ -trait VeyMontConditionImpl[G] { this: VeyMontCondition[G] => +trait SeqGuardImpl[G] { this: SeqGuard[G] => override def t: Type[G] = TBool() override def precedence: Int = Precedence.AND override def layout(implicit ctx: Ctx): Doc = - Group(Doc.fold(condition.map(_._2).map(assoc))(_ <+> "&&" <+/> _)) + Group(Doc.fold(conditions.map(_._2).map(assoc))(_ <+> "&&" <+/> _)) } diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index e1efd5214d..a77310e4a3 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -1593,7 +1593,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends AbstractRewriter[Pr case Z3SeqSuffixOf(post, seq) => Z3SeqSuffixOf(z3seq(post)._1, z3seq(seq)._1) case Z3SeqUnit(arg) => Z3SeqUnit(arg) case Z3TransitiveClosure(ref, args) => Z3TransitiveClosure(ref, coerceArgs(args, ref.ref.decl)) - case VeyMontCondition(c) => VeyMontCondition(c) + case SeqGuard(c) => SeqGuard(c) case localIncoming: BipLocalIncomingData[Pre] => localIncoming case glue: JavaBipGlue[Pre] => glue case LlvmLocal(name) => e diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index f554c76c6b..0f0d1187c1 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -194,6 +194,8 @@ case class SilverTransformation // VeyMont sequential program encoding GenerateSeqProgPermissions, + InferSeqGuards, + EncodeBranchUnanimity, EncodeSeqProg, EncodeString, // Encode spec string as seq diff --git a/src/rewrite/vct/rewrite/veymont/AddVeyMontConditionNodes.scala b/src/rewrite/vct/rewrite/veymont/AddVeyMontConditionNodes.scala deleted file mode 100644 index c11e027044..0000000000 --- a/src/rewrite/vct/rewrite/veymont/AddVeyMontConditionNodes.scala +++ /dev/null @@ -1,108 +0,0 @@ -package vct.col.rewrite.veymont - - -import hre.util.ScopedStack -import vct.col.ast.{And, Block, BooleanValue, Branch, Declaration, Expr, Loop, Node, Statement, VeyMontCondition, SeqProg, Endpoint} -import vct.col.ref.Ref -import vct.col.rewrite.veymont.AddVeyMontAssignmentNodes.{getDerefsFromExpr, getThreadDeref} -import vct.col.rewrite.veymont.AddVeyMontConditionNodes.AddVeyMontConditionError -import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} -import vct.result.VerificationError.UserError - -import scala.reflect.internal.util.TriState.True - -object AddVeyMontConditionNodes extends RewriterBuilder { - override def key: String = "addVeyMontConditionNodes" - - override def desc: String = "Add nodes for VeyMont conditions" - - case class AddVeyMontConditionError(node : Node[_], msg: String) extends UserError { - override def code: String = "addVeyMontConditionError" - - override def text: String = node.o.messageInContext(msg) - } -} - -case class AddVeyMontConditionNodes[Pre <: Generation]() extends Rewriter[Pre] { - - val inSeqProg: ScopedStack[Int] = ScopedStack() - - override def dispatch(decl: Declaration[Pre]): Unit = - decl match { - case dcl: SeqProg[Pre] => - inSeqProg.push(dcl.endpoints.size) - try { - rewriteDefault(dcl) - } finally { - inSeqProg.pop() - } - case _ => rewriteDefault(decl) - } - - override def dispatch(st: Statement[Pre]): Statement[Post] = { - if(inSeqProg.nonEmpty) { - st match { - case Branch(branches) => { - val postbranches = branches.map{case (c,s) => rewriteBranch(c,s)} - Branch(postbranches)(st.o) - } - case l: Loop[Pre] => - rewriteLoop(l) - case _ => rewriteDefault(st) - } - } else rewriteDefault(st) - } - - private def rewriteBranch(cond: Expr[Pre], st: Statement[Pre]) : (Expr[Post],Statement[Post]) = { - val condMap = checkConditionAndGetConditionMap(cond) - if(condMap.isEmpty) //in case of else statement - (dispatch(cond),dispatch(st)) - else (VeyMontCondition[Post](condMap.toList)(st.o), dispatch(st)) - } - - private def rewriteLoop(l: Loop[Pre]): Loop[Post] = { - val condMap = checkConditionAndGetConditionMap(l.cond) - if (condMap.isEmpty) - throw AddVeyMontConditionError(l.cond, "Conditions of loops cannot be `true'!") - else Loop(rewriteDefault(l.init), - VeyMontCondition[Post](condMap.toList)(l.cond.o), - rewriteDefault(l.update), - rewriteDefault(l.contract), - dispatch(l.body))(l.o) - } - - private def checkConditionAndGetConditionMap(e: Expr[Pre]): Map[Ref[Post, Endpoint[Post]], Expr[Post]] = { - if (isTrue(e)) - Map.empty - else { - val condEls = collectConditionElements(e) - val m = getConditionMap(condEls,e) - if(m.keys.toSet.size == inSeqProg.top) { - m - } else throw AddVeyMontConditionError(e, "Conditions of if/while need to reference each thread exactly once!") - } - } - - private def getConditionMap(condEls: List[Expr[Pre]], e : Expr[Pre]): Map[Ref[Post, Endpoint[Post]], Expr[Post]] = { - val derefs = condEls.map(el => (getDerefsFromExpr(el), el)) - derefs.foldRight(Map.empty[Ref[Post, Endpoint[Post]], Expr[Post]]) { case ((d, el), m) => - if (d.size != 1) - throw AddVeyMontConditionError(e, "Conditions of if/while need to reference each thread exactly once!") - else { - val thread = getThreadDeref(d.head, AddVeyMontConditionError(e, "Conditions of if/while can only reference threads, so nothing else!")) - m + (succ(thread) -> dispatch(el)) - } - } - } - - private def isTrue(e : Expr[Pre]) = e match { - case BooleanValue(value) => value - case _ => false - } - - private def collectConditionElements(e: Expr[Pre]) : List[Expr[Pre]] = e match { - case And(left,right) => collectConditionElements(left) ++ collectConditionElements(right) - case _ => List(e) - } - -} diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSequentialBranchUnanimity.scala b/src/rewrite/vct/rewrite/veymont/EncodeSequentialBranchUnanimity.scala new file mode 100644 index 0000000000..f84a8bf40a --- /dev/null +++ b/src/rewrite/vct/rewrite/veymont/EncodeSequentialBranchUnanimity.scala @@ -0,0 +1,83 @@ +package vct.rewrite.veymont + + +import hre.util.ScopedStack +import vct.col.ast.{And, Block, BooleanValue, Branch, Declaration, Expr, Loop, Node, Statement, SeqGuard, SeqProg, Endpoint} +import vct.col.ref.Ref +import vct.rewrite.veymont.EncodeSequentialBranchUnanimity.AddVeyMontConditionError +import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} +import vct.result.VerificationError.UserError +import vct.col.util.AstBuildHelpers._ +import vct.col.ast.RewriteHelpers._ + +object EncodeSequentialBranchUnanimity extends RewriterBuilder { + override def key: String = "encodeBranchUnanimity" + override def desc: String = "Encodes the branch unanimity requirement imposed by VeyMont on branches and loops in seq_program nodes." + + case class AddVeyMontConditionError(node : Node[_], msg: String) extends UserError { + override def code: String = "addVeyMontConditionError" + override def text: String = node.o.messageInContext(msg) + } +} + +case class EncodeSequentialBranchUnanimity[Pre <: Generation]() extends Rewriter[Pre] { + + val currentProg: ScopedStack[SeqProg[Pre]] = ScopedStack() +// +// override def dispatch(decl: Declaration[Pre]): Unit = decl match { +// case prog: SeqProg[Pre] => currentProg.having(prog) { +// rewriteDefault(prog) +// } +// case decl => rewriteDefault(decl) +// } +// +// override def dispatch(statement: Statement[Pre]): Statement[Post] = statement match { +// case branch: Branch[Pre] if currentProg.nonEmpty => +// branch.rewrite(branch.branches.map { case (c, s) => rewriteBranch(c, s) }) +// +// case l: Loop[Pre] if currentProg.nonEmpty => +// val condMap = checkConditionAndGetConditionMap(l.cond) +// if (condMap.isEmpty) +// throw AddVeyMontConditionError(l.cond, "Conditions of loops cannot be `true'!") +// else Loop( +// rewriteDefault(l.init), +// SeqGuard[Post](condMap.toList)(l.cond.o), +// rewriteDefault(l.update), +// rewriteDefault(l.contract), +// dispatch(l.body) +// )(l.o) +// +// case statement => rewriteDefault(statement) +// } +// +// private def rewriteBranch(cond: Expr[Pre], st: Statement[Pre]) : (Expr[Post],Statement[Post]) = { +// val condMap = checkConditionAndGetConditionMap(cond) +// if(condMap.isEmpty) //in case of else statement +// (dispatch(cond),dispatch(st)) +// else (SeqGuard[Post](condMap.toList)(st.o), dispatch(st)) +// } +// +// private def checkConditionAndGetConditionMap(e: Expr[Pre]): Map[Ref[Post, Endpoint[Post]], Expr[Post]] = e match { +// case BooleanValue(true) => Map.empty +// case BooleanValue(_) => +// val condEls = unfoldStar(e) +// val m = getConditionMap(condEls,e) +// if(m.keys.toSet.size == currentSeqProg.top) { +// m +// } else +// // TODO: Is this requirement properly handled? +//// throw AddVeyMontConditionError(e, "Conditions of if/while need to reference each thread exactly once!") +// } +// +// private def getConditionMap(condEls: Seq[Expr[Pre]], e : Expr[Pre]): Map[Ref[Post, Endpoint[Post]], Expr[Post]] = { +// val derefs = condEls.map(el => (getDerefsFromExpr(el), el)) +// derefs.foldRight(Map.empty[Ref[Post, Endpoint[Post]], Expr[Post]]) { case ((d, el), m) => +// if (d.size != 1) +// throw AddVeyMontConditionError(e, "Conditions of if/while need to reference each thread exactly once!") +// else { +// val thread = getThreadDeref(d.head, AddVeyMontConditionError(e, "Conditions of if/while can only reference threads, so nothing else!")) +// m + (succ(thread) -> dispatch(el)) +// } +// } +// } +} diff --git a/src/rewrite/vct/rewrite/veymont/InferSeqGuards.scala b/src/rewrite/vct/rewrite/veymont/InferSeqGuards.scala new file mode 100644 index 0000000000..f1f48f2e68 --- /dev/null +++ b/src/rewrite/vct/rewrite/veymont/InferSeqGuards.scala @@ -0,0 +1,77 @@ +package vct.rewrite.veymont + +import hre.util.ScopedStack +import vct.col.ast.{Deref, And, Block, BooleanValue, Branch, Declaration, Endpoint, EndpointUse, Expr, Loop, Node, SeqGuard, SeqProg, Statement} +import vct.col.ref.Ref +import vct.rewrite.veymont.EncodeSequentialBranchUnanimity.AddVeyMontConditionError +import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} +import vct.result.VerificationError.UserError +import vct.col.util.AstBuildHelpers._ +import vct.col.ast.RewriteHelpers._ + +object InferSeqGuards extends RewriterBuilder { + override def key: String = "inferSeqGuards" + override def desc: String = "Lifts conditions in loops and conditionals into the SeqGuard AST node, stratifying the condition per endpoint." +} + +case class InferSeqGuards[Pre <: Generation]() extends Rewriter[Pre] { + val currentProg: ScopedStack[SeqProg[Pre]] = ScopedStack() + + override def dispatch(decl: Declaration[Pre]): Unit = decl match { + case prog: SeqProg[Pre] => currentProg.having(prog) { + rewriteDefault(prog) + } + case decl => rewriteDefault(decl) + } + + override def dispatch(statement: Statement[Pre]): Statement[Post] = statement match { + case branch: Branch[Pre] if currentProg.nonEmpty => + branch.rewrite(branch.branches.map { case (c, s) => + (inferSeqGuard(c), dispatch(s)) + }) + + case l: Loop[Pre] if currentProg.nonEmpty => + l.rewrite(cond = inferSeqGuard(l.cond)) + + case statement => rewriteDefault(statement) + } + + sealed trait Target + case class Targeted(endpoint: Endpoint[Pre]) extends Target + case object Untargeted extends Target + + // Infer guard conditions based on the classic syntactical restrictions - endpoint dereferences determine which + // endpoint is evaluating the expression. + def inferSeqGuard(e: Expr[Pre]): SeqGuard[Post] = { + val targeted = categorizeConditions(e) + + val conditions = + if(targeted.map(_._1).forall(_ == Untargeted)) { + // If all expressions are untargeted, spread the expressions over _all_ endpoints + currentProg.top.endpoints.map { endpoint => + targeted.map { case (_, e) => (endpoint, e) } + } + } else { + // Otherwise, only spread the untargeted expressions over all participating endoints + val participants = targeted.collect { case (Targeted(endpoint), _) => endpoint } + targeted.flatMap { + case (Targeted(endpoint), e) => Seq((endpoint, e)) + case (Untargeted, e) => participants.map((_, e)) + } + } + + SeqGuard[Post](conditions.map { case (endpoint, e) => (succ[Endpoint[Post]](endpoint), dispatch(e))})(e.o) + } + + def categorizeConditions(e: Expr[Pre]): Seq[(Target, Expr[Pre])] = + unfoldStar(e).map(e => (target(e), e)) + + def target(e: Expr[Pre]): Target = { + val endpoints: Seq[Endpoint[Pre]] = e.collect { case Deref(EndpointUse(Ref(endpoint)), _) => endpoint } + endpoints match { + case Seq(endpoint) => Targeted(endpoint) // expr is totally in context of one endpoint and whatever else is in scope + case Seq() => Untargeted // Expr doesn't use any endpoints - probably fine? + case _ => ??? // Expr uses multiple endpoints - for now we should disallow that. TODO: Throw error + } + } +} diff --git a/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala b/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala index de4e0d2367..7deb00cee0 100644 --- a/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala +++ b/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala @@ -2,7 +2,7 @@ package vct.rewrite.veymont import hre.util.ScopedStack import vct.col.ast.RewriteHelpers.{RewriteApplicableContract, RewriteClass, RewriteDeref, RewriteJavaClass, RewriteJavaConstructor, RewriteMethodInvocation} -import vct.col.ast.{AbstractRewriter, ApplicableContract, Assert, Assign, Block, BooleanValue, Branch, Class, ClassDeclaration, CommunicateX, Declaration, Deref, Endpoint, EndpointUse, Eval, Expr, InstanceField, InstanceMethod, JavaClass, JavaConstructor, JavaInvocation, JavaLocal, JavaMethod, JavaNamedType, JavaParam, JavaPublic, JavaTClass, Local, Loop, MethodInvocation, NewObject, Node, Procedure, Program, RunMethod, Scope, SeqProg, SeqRun, Statement, TClass, TVeyMontChannel, TVoid, ThisObject, ThisSeqProg, Type, UnitAccountedPredicate, Variable, VeyMontAssignExpression, VeyMontCondition} +import vct.col.ast.{AbstractRewriter, ApplicableContract, Assert, Assign, Block, BooleanValue, Branch, Class, ClassDeclaration, CommunicateX, Declaration, Deref, Endpoint, EndpointUse, Eval, Expr, InstanceField, InstanceMethod, JavaClass, JavaConstructor, JavaInvocation, JavaLocal, JavaMethod, JavaNamedType, JavaParam, JavaPublic, JavaTClass, Local, Loop, MethodInvocation, NewObject, Node, Procedure, Program, RunMethod, Scope, SeqProg, SeqRun, Statement, TClass, TVeyMontChannel, TVoid, ThisObject, ThisSeqProg, Type, UnitAccountedPredicate, Variable, VeyMontAssignExpression, SeqGuard} import vct.col.origin.Origin import vct.col.resolve.ctx.RefJavaMethod import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder, RewriterBuilderArg, Rewritten} @@ -291,7 +291,7 @@ case class ParalleliseEndpoints[Pre <: Generation](channelClass: JavaClass[_]) e if(threadBuildingBlocks.nonEmpty) { val thread = threadBuildingBlocks.top.thread node match { - case c: VeyMontCondition[Pre] => paralleliseThreadCondition(node, thread, c) + case c: SeqGuard[Pre] => paralleliseThreadCondition(node, thread, c) case m: MethodInvocation[Pre] => updateThreadRefMethodInvoc(thread, m) case d: Deref[Pre] => updateThreadRefInDeref(node, thread, d) case t: EndpointUse[Pre] => updateThreadRefVeyMontDeref(node, thread, t) @@ -326,8 +326,8 @@ case class ParalleliseEndpoints[Pre <: Generation](channelClass: JavaClass[_]) e } } - private def paralleliseThreadCondition(node: Expr[Pre], thread: Endpoint[Pre], c: VeyMontCondition[Pre]) = { - c.condition.find { case (threadRef, _) => + private def paralleliseThreadCondition(node: Expr[Pre], thread: Endpoint[Pre], c: SeqGuard[Pre]) = { + c.conditions.find { case (threadRef, _) => threadRef.decl == thread } match { case Some((_, threadExpr)) => dispatch(threadExpr) From 8a5450baa987ee02cdbd505083ac31831dccb6f0 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Fri, 10 Nov 2023 16:42:28 +0100 Subject: [PATCH 02/32] First draft of branch unanimity for ifs --- src/col/vct/col/ast/Node.scala | 7 +- .../ast/declaration/global/SeqProgImpl.scala | 4 +- .../col/ast/expr/op/bool/SeqGuardImpl.scala | 12 --- .../ast/family/seqguard/SeqGuardImpl.scala | 7 ++ .../vct/col/ast/statement/StatementImpl.scala | 6 +- .../ast/statement/veymont/SeqBranchImpl.scala | 38 +++++++++ src/col/vct/col/check/Check.scala | 21 ++++- .../vct/col/rewrite/NonLatchingRewriter.scala | 1 + .../vct/col/typerules/CoercingRewriter.scala | 12 ++- src/col/vct/col/util/AstBuildHelpers.scala | 3 + src/main/vct/main/stages/Transformation.scala | 10 +-- .../ResolveExpressionSideEffects.scala | 1 + .../veymont/EncodeSeqBranchUnanimity.scala | 69 +++++++++++++++ .../EncodeSequentialBranchUnanimity.scala | 83 ------------------- .../vct/rewrite/veymont/InferSeqGuards.scala | 77 ----------------- .../veymont/ParalleliseVeyMontThreads.scala | 17 ++-- .../vct/rewrite/veymont/SplitSeqGuards.scala | 67 +++++++++++++++ .../examples/TechnicalVeyMontSpec.scala | 56 ++++++++++++- 18 files changed, 299 insertions(+), 192 deletions(-) delete mode 100644 src/col/vct/col/ast/expr/op/bool/SeqGuardImpl.scala create mode 100644 src/col/vct/col/ast/family/seqguard/SeqGuardImpl.scala create mode 100644 src/col/vct/col/ast/statement/veymont/SeqBranchImpl.scala create mode 100644 src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala delete mode 100644 src/rewrite/vct/rewrite/veymont/EncodeSequentialBranchUnanimity.scala delete mode 100644 src/rewrite/vct/rewrite/veymont/InferSeqGuards.scala create mode 100644 src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index 5125655c84..c7114e0937 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -56,6 +56,7 @@ import vct.col.ast.family.location._ import vct.col.ast.family.loopcontract._ import vct.col.ast.family.parregion._ import vct.col.ast.family.pvlcommunicate.{PVLCommunicateAccessImpl, PVLCommunicateImpl, PVLCommunicateSubjectImpl, PVLEndpointNameImpl, PVLFamilyRangeImpl, PVLIndexedFamilyNameImpl} +import vct.col.ast.family.seqguard.SeqGuardImpl import vct.col.ast.family.seqrun.SeqRunImpl import vct.col.ast.family.signals._ import vct.col.ast.family.subject.EndpointNameImpl @@ -1272,7 +1273,11 @@ case class Access[G](subject: Subject[G], field: Ref[G, InstanceField[G]])(impli final case class Communicate[G](receiver: Access[G], sender: Access[G])(implicit val o: Origin) extends Statement[G] with CommunicateImpl[G] final case class SeqAssign[G](receiver: Ref[G, Endpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(implicit val o: Origin) extends Statement[G] with SeqAssignImpl[G] final case class EndpointUse[G](ref: Ref[G, Endpoint[G]])(implicit val o: Origin) extends Expr[G] with EndpointUseImpl[G] -final case class SeqGuard[G](conditions: Seq[(Ref[G, Endpoint[G]], Expr[G])])(implicit val o: Origin) extends Expr[G] with SeqGuardImpl[G] + +sealed trait SeqGuard[G] extends NodeFamily[G] with SeqGuardImpl[G] +final case class EndpointGuard[G](endpoint: Ref[G, Endpoint[G]], condition: Expr[G])(implicit val o: Origin) extends SeqGuard[G] +final case class UnpointedGuard[G](condition: Expr[G])(implicit val o: Origin) extends SeqGuard[G] +final case class SeqBranch[G](guards: Seq[SeqGuard[G]], yes: Statement[G], no: Option[Statement[G]])(implicit val o: Origin) extends Statement[G] with SeqBranchImpl[G] final case class VeyMontAssignExpression[G](endpoint: Ref[G, Endpoint[G]], assign: Statement[G])(implicit val o: Origin) extends Statement[G] with VeyMontAssignExpressionImpl[G] final case class CommunicateX[G](receiver: Ref[G, Endpoint[G]], sender: Ref[G, Endpoint[G]], chanType: Type[G], assign: Statement[G])(implicit val o: Origin) extends Statement[G] with CommunicateXImpl[G] diff --git a/src/col/vct/col/ast/declaration/global/SeqProgImpl.scala b/src/col/vct/col/ast/declaration/global/SeqProgImpl.scala index ddbf0cb983..2f3d423cd7 100644 --- a/src/col/vct/col/ast/declaration/global/SeqProgImpl.scala +++ b/src/col/vct/col/ast/declaration/global/SeqProgImpl.scala @@ -18,5 +18,7 @@ trait SeqProgImpl[G] extends Declarator[G] { this: SeqProg[G] => )) override def enterCheckContext(context: CheckContext[G]): CheckContext[G] = - super.enterCheckContext(context).withSeqProg(this) + super.enterCheckContext(context) + .withSeqProg(this) + .withCurrentParticipatingEndpoints(endpoints) } diff --git a/src/col/vct/col/ast/expr/op/bool/SeqGuardImpl.scala b/src/col/vct/col/ast/expr/op/bool/SeqGuardImpl.scala deleted file mode 100644 index f9df8e699c..0000000000 --- a/src/col/vct/col/ast/expr/op/bool/SeqGuardImpl.scala +++ /dev/null @@ -1,12 +0,0 @@ -package vct.col.ast.expr.op.bool - -import vct.col.ast.{TBool, Type, SeqGuard} -import vct.col.print._ - -trait SeqGuardImpl[G] { this: SeqGuard[G] => - override def t: Type[G] = TBool() - - override def precedence: Int = Precedence.AND - override def layout(implicit ctx: Ctx): Doc = - Group(Doc.fold(conditions.map(_._2).map(assoc))(_ <+> "&&" <+/> _)) -} diff --git a/src/col/vct/col/ast/family/seqguard/SeqGuardImpl.scala b/src/col/vct/col/ast/family/seqguard/SeqGuardImpl.scala new file mode 100644 index 0000000000..0286081a9e --- /dev/null +++ b/src/col/vct/col/ast/family/seqguard/SeqGuardImpl.scala @@ -0,0 +1,7 @@ +package vct.col.ast.family.seqguard + +import vct.col.ast.{Expr, SeqGuard} + +trait SeqGuardImpl[G] { this: SeqGuard[G] => + def condition: Expr[G] +} diff --git a/src/col/vct/col/ast/statement/StatementImpl.scala b/src/col/vct/col/ast/statement/StatementImpl.scala index 03586a1918..4331931efc 100644 --- a/src/col/vct/col/ast/statement/StatementImpl.scala +++ b/src/col/vct/col/ast/statement/StatementImpl.scala @@ -1,10 +1,9 @@ package vct.col.ast.statement -import vct.col.ast.{Assert, Assign, Block, Branch, Communicate, CommunicateX, EndpointUse, Eval, Loop, MethodInvocation, SeqAssign, Scope, Statement, ThisSeqProg, VeyMontAssignExpression} import vct.col.ast.node.NodeFamilyImpl +import vct.col.ast._ import vct.col.check.{CheckContext, CheckError, SeqProgStatement} import vct.col.print._ -import vct.col.ref.Ref trait StatementImpl[G] extends NodeFamilyImpl[G] { this: Statement[G] => def layoutAsBlock(implicit ctx: Ctx): Doc = @@ -26,7 +25,8 @@ trait StatementImpl[G] extends NodeFamilyImpl[G] { this: Statement[G] => _: Scope[G] | _: Block[G] | _: Eval[G] | - _: Assert[G] => Seq() + _: Assert[G] | + _: SeqBranch[G] => Seq() case _ => Seq(SeqProgStatement(this)) } }) diff --git a/src/col/vct/col/ast/statement/veymont/SeqBranchImpl.scala b/src/col/vct/col/ast/statement/veymont/SeqBranchImpl.scala new file mode 100644 index 0000000000..8a1b70f6c7 --- /dev/null +++ b/src/col/vct/col/ast/statement/veymont/SeqBranchImpl.scala @@ -0,0 +1,38 @@ +package vct.col.ast.statement.veymont + +import vct.col.ast.{Endpoint, EndpointGuard, SeqBranch, UnpointedGuard} +import vct.col.ast.statement.StatementImpl +import vct.col.check.{CheckContext, CheckError, SeqProgParticipant} +import vct.col.ref.Ref + +trait SeqBranchImpl[G] extends StatementImpl[G] { this: SeqBranch[G] => + def hasUnpointed: Boolean = guards.exists { case _: UnpointedGuard[G] => true; case _ => false } + def explicitParticipants: Seq[Endpoint[G]] = guards.collect { case EndpointGuard(Ref(endpoint), condition) => endpoint } + + override def enterCheckContext(context: CheckContext[G]): CheckContext[G] = { + // Assume SeqProg sets participatingEndpoints + assert(context.currentParticipatingEndpoints.isDefined) + + if (hasUnpointed) { + // Everyone that is participating keeps participating, as well as any endpoints explicitly mentioned + context.appendCurrentParticipatingEndpoints(explicitParticipants) + } else { + // We can refine the set of participants down to the set of endpoints actually present in the guard + context.withCurrentParticipatingEndpoints(explicitParticipants) + } + } + + override def check(context: CheckContext[G]): Seq[CheckError] = super.check(context) ++ { + // Assume SeqProg sets participatingEndpoints + assert(context.currentParticipatingEndpoints.isDefined) + + // Ensure the set of participants is at most refined + if (Set.from(explicitParticipants).subsetOf(context.currentParticipatingEndpoints.get)) { + Seq() + } else { + // There are participants in this if that have been excluded from participation: error + Seq(SeqProgParticipant(this)) + } + } + +} diff --git a/src/col/vct/col/check/Check.scala b/src/col/vct/col/check/Check.scala index d1395b6cb2..6da3551509 100644 --- a/src/col/vct/col/check/Check.scala +++ b/src/col/vct/col/check/Check.scala @@ -7,6 +7,9 @@ import vct.col.origin.Origin import vct.col.ref.Ref import vct.col.resolve.ResolveReferences +import scala.collection.immutable.ListSet +import scala.collection.mutable + case object Check { def inOrder(check1: => Seq[CheckError], check2: => Seq[CheckError]): Seq[CheckError] = check1 match { @@ -61,7 +64,8 @@ sealed trait CheckError { case SeqProgInstanceMethodBody(m) => Seq(context(m, "An instance method in a `seq_prog` must have a body.")) case SeqProgInstanceMethodNonVoid(m) => Seq(context(m, "An instance method in a `seq_prog` must have return type `void`.")) case SeqProgInvocation(s) => Seq(context(s, "Only invocations on `this` and endpoints are allowed.")) - case SeqProgReceivingEndpoint(e) => Seq(context(e, s"Can only refer to the receiving endpoint of this statement")) + case SeqProgReceivingEndpoint(e) => Seq(context(e, s"Can only refer to the receiving endpoint of this statement.")) + case SeqProgParticipant(s) => Seq(context(s, s"This branch lets an endpoint participant which has been excluded by earlier branches.")) }).mkString(Origin.BOLD_HR, Origin.HR, Origin.BOLD_HR) def subcode: String @@ -124,6 +128,9 @@ case class SeqProgInvocation(s: Statement[_]) extends CheckError { case class SeqProgReceivingEndpoint(e: Expr[_]) extends CheckError { val subcode = "seqProgReceivingEndpoint" } +case class SeqProgParticipant(s: SeqBranch[_]) extends CheckError { + val subcode = "seqProgParticipant" +} case object CheckContext { case class ScopeFrame[G](decls: Seq[Declaration[G]], scanLazily: Seq[Node[G]]) { @@ -144,6 +151,7 @@ case class CheckContext[G] inPostCondition: Boolean = false, currentSeqProg: Option[SeqProg[G]] = None, currentReceiverEndpoint: Option[Endpoint[G]] = None, + currentParticipatingEndpoints: Option[Set[Endpoint[G]]] = None, ) { def withScope(decls: Seq[Declaration[G]]): CheckContext[G] = copy(scopes = scopes :+ CheckContext.ScopeFrame(decls, Nil)) @@ -171,6 +179,17 @@ case class CheckContext[G] def withReceiverEndpoint(endpoint: Endpoint[G]): CheckContext[G] = copy(currentReceiverEndpoint = Some(endpoint)) + def withCurrentParticipatingEndpoints(endpoints: Seq[Endpoint[G]]): CheckContext[G] = + // ListSet to preserve insertion order + copy(currentParticipatingEndpoints = Some(ListSet.from(endpoints))) + + def appendCurrentParticipatingEndpoints(newEndpoints: Seq[Endpoint[G]]): CheckContext[G] = + // ListSet to preserve insertion order + currentParticipatingEndpoints match { + case None => withCurrentParticipatingEndpoints(newEndpoints) + case Some(endpoints) => copy(currentParticipatingEndpoints = Some(endpoints.union(ListSet.from(newEndpoints)))) + } + def inScope[Decl <: Declaration[G]](ref: Ref[G, Decl]): Boolean = !undeclared.exists(_.contains(ref.decl)) && scopes.exists(_.contains(ref.decl)) diff --git a/src/col/vct/col/rewrite/NonLatchingRewriter.scala b/src/col/vct/col/rewrite/NonLatchingRewriter.scala index 0910f5b1b3..7771fb7579 100644 --- a/src/col/vct/col/rewrite/NonLatchingRewriter.scala +++ b/src/col/vct/col/rewrite/NonLatchingRewriter.scala @@ -70,4 +70,5 @@ class NonLatchingRewriter[Pre, Post]() extends AbstractRewriter[Pre, Post] { override def dispatch(node: SeqRun[Pre]): SeqRun[Post] = rewriteDefault(node) override def dispatch(node: Access[Pre]): Access[Post] = rewriteDefault(node) override def dispatch(node: Subject[Pre]): Subject[Post] = rewriteDefault(node) + override def dispatch(node: SeqGuard[Pre]): SeqGuard[Post] = rewriteDefault(node) } \ No newline at end of file diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index a77310e4a3..e004f82d4f 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -260,6 +260,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends AbstractRewriter[Pr case node: SeqRun[Pre] => node case node: Access[Pre] => node case node: Subject[Pre] => node + case node: SeqGuard[Pre] => coerce(node) } def preCoerce(e: Expr[Pre]): Expr[Pre] = e @@ -478,6 +479,10 @@ abstract class CoercingRewriter[Pre <: Generation]() extends AbstractRewriter[Pr def postCoerce(node: SeqRun[Pre]): SeqRun[Post] = rewriteDefault(node) override final def dispatch(node: SeqRun[Pre]): SeqRun[Post] = postCoerce(coerce(preCoerce(node))) + def preCoerce(node: SeqGuard[Pre]): SeqGuard[Pre] = node + def postCoerce(node: SeqGuard[Pre]): SeqGuard[Post] = rewriteDefault(node) + override final def dispatch(node: SeqGuard[Pre]): SeqGuard[Post] = postCoerce(coerce(preCoerce(node))) + def coerce(value: Expr[Pre], target: Type[Pre]): Expr[Pre] = ApplyCoercion(value, CoercionUtils.getCoercion(value.t, target) match { case Some(coercion) => coercion @@ -1593,7 +1598,6 @@ abstract class CoercingRewriter[Pre <: Generation]() extends AbstractRewriter[Pr case Z3SeqSuffixOf(post, seq) => Z3SeqSuffixOf(z3seq(post)._1, z3seq(seq)._1) case Z3SeqUnit(arg) => Z3SeqUnit(arg) case Z3TransitiveClosure(ref, args) => Z3TransitiveClosure(ref, coerceArgs(args, ref.ref.decl)) - case SeqGuard(c) => SeqGuard(c) case localIncoming: BipLocalIncomingData[Pre] => localIncoming case glue: JavaBipGlue[Pre] => glue case LlvmLocal(name) => e @@ -1689,6 +1693,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends AbstractRewriter[Pr println(err.text) throw err } + case s: SeqBranch[Pre] => s } } @@ -2169,4 +2174,9 @@ abstract class CoercingRewriter[Pre <: Generation]() extends AbstractRewriter[Pr def coerce(node: SeqRun[Pre]): SeqRun[Pre] = node def coerce(node: Access[Pre]): Access[Pre] = node def coerce(node: Subject[Pre]): Subject[Pre] = node + def coerce(node: SeqGuard[Pre]): SeqGuard[Pre] = node match { + case EndpointGuard(endpoint, cond) => EndpointGuard(endpoint, bool(cond))(node.o) + case UnpointedGuard(cond) => UnpointedGuard(bool(cond))(node.o) + } + } diff --git a/src/col/vct/col/util/AstBuildHelpers.scala b/src/col/vct/col/util/AstBuildHelpers.scala index 9bb022debb..015e3fa526 100644 --- a/src/col/vct/col/util/AstBuildHelpers.scala +++ b/src/col/vct/col/util/AstBuildHelpers.scala @@ -471,4 +471,7 @@ object AstBuildHelpers { def foldOr[G](exprs: Seq[Expr[G]])(implicit o: Origin): Expr[G] = exprs.reduceOption(Or(_, _)).getOrElse(ff) + + def foldAnd[G](exprs: Seq[Expr[G]])(implicit o: Origin): Expr[G] = + exprs.reduceOption(And(_, _)).getOrElse(tt) } diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index 0f0d1187c1..8f9cfefd4f 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -14,7 +14,7 @@ import vct.col.rewrite.adt._ import vct.col.rewrite.bip._ import vct.col.rewrite.exc._ import vct.col.rewrite.lang.NoSupportSelfLoop -import vct.col.rewrite.veymont.{AddVeyMontAssignmentNodes, AddVeyMontConditionNodes, StructureCheck} +import vct.col.rewrite.veymont.{AddVeyMontAssignmentNodes, StructureCheck} import vct.importer.{PathAdtImporter, Util} import vct.main.Main.TemporarilyUnsupported import vct.main.stages.Transformation.TransformationCheckError @@ -24,7 +24,7 @@ import vct.resources.Resources import vct.result.VerificationError.SystemError import vct.rewrite.{EncodeResourceValues, ExplicitResourceValues, HeapVariableToRef} import vct.rewrite.lang.ReplaceSYCLTypes -import vct.rewrite.veymont.{EncodeSeqProg, GenerateSeqProgPermissions} +import vct.rewrite.veymont.{EncodeSeqBranchUnanimity, EncodeSeqProg, GenerateSeqProgPermissions, SplitSeqGuards} object Transformation { case class TransformationCheckError(pass: RewriterBuilder, errors: Seq[(Program[_], CheckError)]) extends SystemError { @@ -194,8 +194,8 @@ case class SilverTransformation // VeyMont sequential program encoding GenerateSeqProgPermissions, - InferSeqGuards, - EncodeBranchUnanimity, + SplitSeqGuards, + EncodeSeqBranchUnanimity, EncodeSeqProg, EncodeString, // Encode spec string as seq @@ -310,7 +310,7 @@ case class VeyMontTransformation(override val onBeforePassKey: Seq[(String, Veri override val onAfterPassKey: Seq[(String, Verification[_ <: Generation] => Unit)] = Nil) extends Transformation(onBeforePassKey, onAfterPassKey, Seq( AddVeyMontAssignmentNodes, - AddVeyMontConditionNodes, +// AddVeyMontConditionNodes, StructureCheck, )) diff --git a/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala b/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala index 18b7c065ad..762b39123a 100644 --- a/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala +++ b/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala @@ -351,6 +351,7 @@ case class ResolveExpressionSideEffects[Pre <: Generation]() extends Rewriter[Pr case comm: CommunicateX[Pre] => rewriteDefault(comm) case comm: PVLCommunicate[Pre] => rewriteDefault(comm) case comm: Communicate[Pre] => rewriteDefault(comm) + case _: SeqBranch[Pre] => throw ExtraNode case _: CStatement[Pre] => throw ExtraNode case _: CPPStatement[Pre] => throw ExtraNode case _: JavaStatement[Pre] => throw ExtraNode diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala b/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala new file mode 100644 index 0000000000..81ba968da2 --- /dev/null +++ b/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala @@ -0,0 +1,69 @@ +package vct.rewrite.veymont + +import vct.col.ast._ +import vct.col.origin.PanicBlame +import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} +import vct.col.util.AstBuildHelpers._ +import vct.col.util.SuccessionMap +import vct.result.VerificationError.UserError + +object EncodeSeqBranchUnanimity extends RewriterBuilder { + override def key: String = "encodeBranchUnanimity" + override def desc: String = "Encodes the branch unanimity requirement imposed by VeyMont on branches and loops in seq_program nodes." + + case class AddVeyMontConditionError(node : Node[_], msg: String) extends UserError { + override def code: String = "addVeyMontConditionError" + override def text: String = node.o.messageInContext(msg) + } +} + +case class EncodeSeqBranchUnanimity[Pre <: Generation]() extends Rewriter[Pre] { + + val guardSucc = SuccessionMap[SeqGuard[Pre], Variable[Post]]() + + override def dispatch(statement: Statement[Pre]): Statement[Post] = statement match { + case SeqBranch(guards, yes, no) => + implicit val o = statement.o + /* + for each c in conds: + bool bc = c; + + for each subsequent c1, c2 in conds: + assert c1 == c2 + + boolean cond = foldStar(succ(c)) + + if (cons) dispatch(yes) dispatch(no) + */ + val assignments: Seq[Assign[Post]] = guards.map { guard => + guardSucc(guard) = new Variable(TBool()) + assignLocal(guardSucc(guard).get, dispatch(guard.condition)) + } + + val assertions: Seq[Assert[Post]] = (0 until guards.length - 1).map { i => + val c1 = guards(i) + val c2 = guards(i + 1) + Assert(guardSucc(c1).get === guardSucc(c2).get)(PanicBlame("TODO: Implement failing assert")) + } + + val majorCond = new Variable[Post](TBool()) + val majorAssign: Assign[Post] = assignLocal[Post]( + majorCond.get, + foldAnd(guards.map(guard => guardSucc(guard).get)) + ) + + val finalIf: Branch[Post] = Branch[Post](Seq( + (majorCond.get, dispatch(yes)), + (tt, no.map(dispatch).getOrElse(Block(Seq()))) + )) + + Scope(guards.map(guardSucc(_)) :+ majorCond, Block( + assignments ++ + assertions :+ + majorAssign :+ + finalIf + )) + + case statement => rewriteDefault(statement) + } +} diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSequentialBranchUnanimity.scala b/src/rewrite/vct/rewrite/veymont/EncodeSequentialBranchUnanimity.scala deleted file mode 100644 index f84a8bf40a..0000000000 --- a/src/rewrite/vct/rewrite/veymont/EncodeSequentialBranchUnanimity.scala +++ /dev/null @@ -1,83 +0,0 @@ -package vct.rewrite.veymont - - -import hre.util.ScopedStack -import vct.col.ast.{And, Block, BooleanValue, Branch, Declaration, Expr, Loop, Node, Statement, SeqGuard, SeqProg, Endpoint} -import vct.col.ref.Ref -import vct.rewrite.veymont.EncodeSequentialBranchUnanimity.AddVeyMontConditionError -import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} -import vct.result.VerificationError.UserError -import vct.col.util.AstBuildHelpers._ -import vct.col.ast.RewriteHelpers._ - -object EncodeSequentialBranchUnanimity extends RewriterBuilder { - override def key: String = "encodeBranchUnanimity" - override def desc: String = "Encodes the branch unanimity requirement imposed by VeyMont on branches and loops in seq_program nodes." - - case class AddVeyMontConditionError(node : Node[_], msg: String) extends UserError { - override def code: String = "addVeyMontConditionError" - override def text: String = node.o.messageInContext(msg) - } -} - -case class EncodeSequentialBranchUnanimity[Pre <: Generation]() extends Rewriter[Pre] { - - val currentProg: ScopedStack[SeqProg[Pre]] = ScopedStack() -// -// override def dispatch(decl: Declaration[Pre]): Unit = decl match { -// case prog: SeqProg[Pre] => currentProg.having(prog) { -// rewriteDefault(prog) -// } -// case decl => rewriteDefault(decl) -// } -// -// override def dispatch(statement: Statement[Pre]): Statement[Post] = statement match { -// case branch: Branch[Pre] if currentProg.nonEmpty => -// branch.rewrite(branch.branches.map { case (c, s) => rewriteBranch(c, s) }) -// -// case l: Loop[Pre] if currentProg.nonEmpty => -// val condMap = checkConditionAndGetConditionMap(l.cond) -// if (condMap.isEmpty) -// throw AddVeyMontConditionError(l.cond, "Conditions of loops cannot be `true'!") -// else Loop( -// rewriteDefault(l.init), -// SeqGuard[Post](condMap.toList)(l.cond.o), -// rewriteDefault(l.update), -// rewriteDefault(l.contract), -// dispatch(l.body) -// )(l.o) -// -// case statement => rewriteDefault(statement) -// } -// -// private def rewriteBranch(cond: Expr[Pre], st: Statement[Pre]) : (Expr[Post],Statement[Post]) = { -// val condMap = checkConditionAndGetConditionMap(cond) -// if(condMap.isEmpty) //in case of else statement -// (dispatch(cond),dispatch(st)) -// else (SeqGuard[Post](condMap.toList)(st.o), dispatch(st)) -// } -// -// private def checkConditionAndGetConditionMap(e: Expr[Pre]): Map[Ref[Post, Endpoint[Post]], Expr[Post]] = e match { -// case BooleanValue(true) => Map.empty -// case BooleanValue(_) => -// val condEls = unfoldStar(e) -// val m = getConditionMap(condEls,e) -// if(m.keys.toSet.size == currentSeqProg.top) { -// m -// } else -// // TODO: Is this requirement properly handled? -//// throw AddVeyMontConditionError(e, "Conditions of if/while need to reference each thread exactly once!") -// } -// -// private def getConditionMap(condEls: Seq[Expr[Pre]], e : Expr[Pre]): Map[Ref[Post, Endpoint[Post]], Expr[Post]] = { -// val derefs = condEls.map(el => (getDerefsFromExpr(el), el)) -// derefs.foldRight(Map.empty[Ref[Post, Endpoint[Post]], Expr[Post]]) { case ((d, el), m) => -// if (d.size != 1) -// throw AddVeyMontConditionError(e, "Conditions of if/while need to reference each thread exactly once!") -// else { -// val thread = getThreadDeref(d.head, AddVeyMontConditionError(e, "Conditions of if/while can only reference threads, so nothing else!")) -// m + (succ(thread) -> dispatch(el)) -// } -// } -// } -} diff --git a/src/rewrite/vct/rewrite/veymont/InferSeqGuards.scala b/src/rewrite/vct/rewrite/veymont/InferSeqGuards.scala deleted file mode 100644 index f1f48f2e68..0000000000 --- a/src/rewrite/vct/rewrite/veymont/InferSeqGuards.scala +++ /dev/null @@ -1,77 +0,0 @@ -package vct.rewrite.veymont - -import hre.util.ScopedStack -import vct.col.ast.{Deref, And, Block, BooleanValue, Branch, Declaration, Endpoint, EndpointUse, Expr, Loop, Node, SeqGuard, SeqProg, Statement} -import vct.col.ref.Ref -import vct.rewrite.veymont.EncodeSequentialBranchUnanimity.AddVeyMontConditionError -import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} -import vct.result.VerificationError.UserError -import vct.col.util.AstBuildHelpers._ -import vct.col.ast.RewriteHelpers._ - -object InferSeqGuards extends RewriterBuilder { - override def key: String = "inferSeqGuards" - override def desc: String = "Lifts conditions in loops and conditionals into the SeqGuard AST node, stratifying the condition per endpoint." -} - -case class InferSeqGuards[Pre <: Generation]() extends Rewriter[Pre] { - val currentProg: ScopedStack[SeqProg[Pre]] = ScopedStack() - - override def dispatch(decl: Declaration[Pre]): Unit = decl match { - case prog: SeqProg[Pre] => currentProg.having(prog) { - rewriteDefault(prog) - } - case decl => rewriteDefault(decl) - } - - override def dispatch(statement: Statement[Pre]): Statement[Post] = statement match { - case branch: Branch[Pre] if currentProg.nonEmpty => - branch.rewrite(branch.branches.map { case (c, s) => - (inferSeqGuard(c), dispatch(s)) - }) - - case l: Loop[Pre] if currentProg.nonEmpty => - l.rewrite(cond = inferSeqGuard(l.cond)) - - case statement => rewriteDefault(statement) - } - - sealed trait Target - case class Targeted(endpoint: Endpoint[Pre]) extends Target - case object Untargeted extends Target - - // Infer guard conditions based on the classic syntactical restrictions - endpoint dereferences determine which - // endpoint is evaluating the expression. - def inferSeqGuard(e: Expr[Pre]): SeqGuard[Post] = { - val targeted = categorizeConditions(e) - - val conditions = - if(targeted.map(_._1).forall(_ == Untargeted)) { - // If all expressions are untargeted, spread the expressions over _all_ endpoints - currentProg.top.endpoints.map { endpoint => - targeted.map { case (_, e) => (endpoint, e) } - } - } else { - // Otherwise, only spread the untargeted expressions over all participating endoints - val participants = targeted.collect { case (Targeted(endpoint), _) => endpoint } - targeted.flatMap { - case (Targeted(endpoint), e) => Seq((endpoint, e)) - case (Untargeted, e) => participants.map((_, e)) - } - } - - SeqGuard[Post](conditions.map { case (endpoint, e) => (succ[Endpoint[Post]](endpoint), dispatch(e))})(e.o) - } - - def categorizeConditions(e: Expr[Pre]): Seq[(Target, Expr[Pre])] = - unfoldStar(e).map(e => (target(e), e)) - - def target(e: Expr[Pre]): Target = { - val endpoints: Seq[Endpoint[Pre]] = e.collect { case Deref(EndpointUse(Ref(endpoint)), _) => endpoint } - endpoints match { - case Seq(endpoint) => Targeted(endpoint) // expr is totally in context of one endpoint and whatever else is in scope - case Seq() => Untargeted // Expr doesn't use any endpoints - probably fine? - case _ => ??? // Expr uses multiple endpoints - for now we should disallow that. TODO: Throw error - } - } -} diff --git a/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala b/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala index 7deb00cee0..65001978cb 100644 --- a/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala +++ b/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala @@ -291,7 +291,8 @@ case class ParalleliseEndpoints[Pre <: Generation](channelClass: JavaClass[_]) e if(threadBuildingBlocks.nonEmpty) { val thread = threadBuildingBlocks.top.thread node match { - case c: SeqGuard[Pre] => paralleliseThreadCondition(node, thread, c) + // TODO: Disabled this because the AST changed, repair + // case c: SeqGuard[Pre] => paralleliseThreadCondition(node, thread, c) case m: MethodInvocation[Pre] => updateThreadRefMethodInvoc(thread, m) case d: Deref[Pre] => updateThreadRefInDeref(node, thread, d) case t: EndpointUse[Pre] => updateThreadRefVeyMontDeref(node, thread, t) @@ -327,12 +328,14 @@ case class ParalleliseEndpoints[Pre <: Generation](channelClass: JavaClass[_]) e } private def paralleliseThreadCondition(node: Expr[Pre], thread: Endpoint[Pre], c: SeqGuard[Pre]) = { - c.conditions.find { case (threadRef, _) => - threadRef.decl == thread - } match { - case Some((_, threadExpr)) => dispatch(threadExpr) - case _ => throw ParalleliseEndpointsError(node, "Condition of if statement or while loop must contain an expression for every thread") - } + ??? + // TODO: Broke this because AST changed, repair +// c.conditions.find { case (threadRef, _) => +// threadRef.decl == thread +// } match { +// case Some((_, threadExpr)) => dispatch(threadExpr) +// case _ => throw ParalleliseEndpointsError(node, "Condition of if statement or while loop must contain an expression for every thread") +// } } private def getThisVeyMontDeref(thread: Endpoint[Pre], o: Origin, threadField: InstanceField[Rewritten[Pre]]) = { diff --git a/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala b/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala new file mode 100644 index 0000000000..26a207b24e --- /dev/null +++ b/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala @@ -0,0 +1,67 @@ +package vct.rewrite.veymont + +import hre.util.ScopedStack +import vct.col.ast._ +import vct.col.origin.Origin +import vct.col.ref.Ref +import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} +import vct.col.util.AstBuildHelpers._ +import vct.result.VerificationError.UserError +import vct.rewrite.veymont.SplitSeqGuards.MultipleEndpoints + +object SplitSeqGuards extends RewriterBuilder { + override def key: String = "inferSeqGuards" + override def desc: String = "Lifts conditions in loops and conditionals into the SeqGuard AST node, stratifying the condition per endpoint." + + case class MultipleEndpoints(e: Expr[_]) extends UserError { + override def code: String = "multipleEndpoints" + override def text: String = e.o.messageInContext("This expression references multiple endpoints, but that is not yet supported.") + } +} + +case class SplitSeqGuards[Pre <: Generation]() extends Rewriter[Pre] { + val currentProg: ScopedStack[SeqProg[Pre]] = ScopedStack() + + override def dispatch(decl: Declaration[Pre]): Unit = decl match { + case prog: SeqProg[Pre] => + currentProg.having(prog) { + rewriteDefault(prog) + } + case decl => rewriteDefault(decl) + } + + override def dispatch(statement: Statement[Pre]): Statement[Post] = statement match { + case branch: Branch[Pre] if currentProg.nonEmpty => + assert(branch.branches.nonEmpty) + unfoldBranch(branch.branches)(branch.o) + + case l: Loop[Pre] if currentProg.nonEmpty => + ??? + + case statement => rewriteDefault(statement) + } + + def unfoldBranch(branches: Seq[(Expr[Pre], Statement[Pre])])(implicit o: Origin): SeqBranch[Post] = branches match { + case Seq((e, s)) => SeqBranch(inferSeqGuard(e), dispatch(s), None) + case (e, s) +: (otherYes +: branches) => + SeqBranch(inferSeqGuard(e), dispatch(s), Some(unfoldBranch(otherYes +: branches))) + } + + // Infer guard conditions based on the classic syntactical restrictions - endpoint dereferences determine which + // endpoint is evaluating the expression. + def inferSeqGuard(e: Expr[Pre]): Seq[SeqGuard[Post]] = + unfoldStar(e).map(target) + + // This method makes an experssion targeted, in the sense that: if it is syntactically obvious the expression is to be + // executed in the context of a certain endpoint, it is marked as a SeqGuard belonging to that endpoint + def target(e: Expr[Pre]): SeqGuard[Post] = { + val endpoints: Seq[Endpoint[Pre]] = e.collect { case Deref(EndpointUse(Ref(endpoint)), _) => endpoint } + endpoints match { + case Seq(endpoint) => + // expr is totally in context of one endpoint and whatever else is in scope + EndpointGuard[Post](succ(endpoint), dispatch(e))(e.o) + case Seq() => UnpointedGuard(dispatch(e))(e.o) // Expr doesn't use any endpoints - probably fine? + case _ => throw MultipleEndpoints(e) // Expr uses multiple endpoints - for now we should disallow that. + } + } +} diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index 9428c0d758..45fb62113a 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -3,7 +3,6 @@ package vct.test.integration.examples import vct.test.integration.helper.VercorsSpec class TechnicalVeyMontSpec extends VercorsSpec { - // TODO: Should eventually become pass vercors should verify using silicon in "example using communicate" pvl """ class Storage { @@ -244,4 +243,59 @@ class TechnicalVeyMontSpec extends VercorsSpec { } } """ + + vercors should fail withCode "??? error missing" using silicon in "Parts of condition in branch have to agree inside seqprog" pvl + """ + class Storage { + int x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + + seq_run { + if (alice.x == 0 && bob.x == 0) { + // Alice might go here, bob might not: error + } + } + } + """ + + vercors should fail withCode "??? error missing" using silicon in "Parts of condition in branch have to agree inside seqprog, including conditions for all endpoints" pvl + """ + class Storage { + int x; + } + + pure int f() = 3; + + seq_program Example() { + endpoint alice = Storage(); + + seq_run { + if (alice.x == 0 && f() == 3) { + // Alice might go here, will definitely, because of the second expression: error + } + } + } + """ + + vercors should error withCode "??? error missing" in "`if` cannot depend on bob, inside an `if` depending on alice" pvl + """ + class Storage { + int x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + + seq_run { + if (alice.x == 0) { + if (bob.x == 0) { + // Error + } + } + } + } + """ } From 18895c8ad3f6bc5f9fa581fd56c8b5b9132478dd Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Tue, 14 Nov 2023 16:16:29 +0100 Subject: [PATCH 03/32] Branch unanimity now supports generalized expressions as well. Also, specific checkerrors are reported as usererrors if they are encountered the first time. --- src/col/vct/col/ast/Node.scala | 6 +- .../family/seqguard/EndpointGuardImpl.scala | 7 ++ .../ast/family/seqguard/SeqGuardImpl.scala | 3 +- .../family/seqguard/UnpointedGuardImpl.scala | 7 ++ .../ast/statement/veymont/SeqBranchImpl.scala | 12 ++- src/col/vct/col/check/Check.scala | 2 +- src/main/vct/main/stages/Transformation.scala | 4 +- .../veymont/DeduplicateSeqGuards.scala | 41 +++++++ .../veymont/EncodeSeqBranchUnanimity.scala | 2 +- .../veymont/RemoveUnpointedGuard.scala | 54 ++++++++++ .../vct/rewrite/veymont/SplitSeqGuards.scala | 51 +++++++-- .../examples/TechnicalVeyMontSpec.scala | 100 +++++++++++++++++- 12 files changed, 270 insertions(+), 19 deletions(-) create mode 100644 src/col/vct/col/ast/family/seqguard/EndpointGuardImpl.scala create mode 100644 src/col/vct/col/ast/family/seqguard/UnpointedGuardImpl.scala create mode 100644 src/rewrite/vct/rewrite/veymont/DeduplicateSeqGuards.scala create mode 100644 src/rewrite/vct/rewrite/veymont/RemoveUnpointedGuard.scala diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index c7114e0937..0284023108 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -56,7 +56,7 @@ import vct.col.ast.family.location._ import vct.col.ast.family.loopcontract._ import vct.col.ast.family.parregion._ import vct.col.ast.family.pvlcommunicate.{PVLCommunicateAccessImpl, PVLCommunicateImpl, PVLCommunicateSubjectImpl, PVLEndpointNameImpl, PVLFamilyRangeImpl, PVLIndexedFamilyNameImpl} -import vct.col.ast.family.seqguard.SeqGuardImpl +import vct.col.ast.family.seqguard._ import vct.col.ast.family.seqrun.SeqRunImpl import vct.col.ast.family.signals._ import vct.col.ast.family.subject.EndpointNameImpl @@ -1275,8 +1275,8 @@ final case class SeqAssign[G](receiver: Ref[G, Endpoint[G]], field: Ref[G, Insta final case class EndpointUse[G](ref: Ref[G, Endpoint[G]])(implicit val o: Origin) extends Expr[G] with EndpointUseImpl[G] sealed trait SeqGuard[G] extends NodeFamily[G] with SeqGuardImpl[G] -final case class EndpointGuard[G](endpoint: Ref[G, Endpoint[G]], condition: Expr[G])(implicit val o: Origin) extends SeqGuard[G] -final case class UnpointedGuard[G](condition: Expr[G])(implicit val o: Origin) extends SeqGuard[G] +final case class EndpointGuard[G](endpoint: Ref[G, Endpoint[G]], condition: Expr[G])(implicit val o: Origin) extends SeqGuard[G] with EndpointGuardImpl[G] +final case class UnpointedGuard[G](condition: Expr[G])(implicit val o: Origin) extends SeqGuard[G] with UnpointedGuardImpl[G] final case class SeqBranch[G](guards: Seq[SeqGuard[G]], yes: Statement[G], no: Option[Statement[G]])(implicit val o: Origin) extends Statement[G] with SeqBranchImpl[G] final case class VeyMontAssignExpression[G](endpoint: Ref[G, Endpoint[G]], assign: Statement[G])(implicit val o: Origin) extends Statement[G] with VeyMontAssignExpressionImpl[G] diff --git a/src/col/vct/col/ast/family/seqguard/EndpointGuardImpl.scala b/src/col/vct/col/ast/family/seqguard/EndpointGuardImpl.scala new file mode 100644 index 0000000000..cb0d4b72eb --- /dev/null +++ b/src/col/vct/col/ast/family/seqguard/EndpointGuardImpl.scala @@ -0,0 +1,7 @@ +package vct.col.ast.family.seqguard + +import vct.col.ast.EndpointGuard + +trait EndpointGuardImpl[G] { this: EndpointGuard[G] => + def endpointOpt = Some(endpoint.decl) +} diff --git a/src/col/vct/col/ast/family/seqguard/SeqGuardImpl.scala b/src/col/vct/col/ast/family/seqguard/SeqGuardImpl.scala index 0286081a9e..ed2642d65a 100644 --- a/src/col/vct/col/ast/family/seqguard/SeqGuardImpl.scala +++ b/src/col/vct/col/ast/family/seqguard/SeqGuardImpl.scala @@ -1,7 +1,8 @@ package vct.col.ast.family.seqguard -import vct.col.ast.{Expr, SeqGuard} +import vct.col.ast.{Endpoint, Expr, SeqGuard} trait SeqGuardImpl[G] { this: SeqGuard[G] => def condition: Expr[G] + def endpointOpt: Option[Endpoint[G]] } diff --git a/src/col/vct/col/ast/family/seqguard/UnpointedGuardImpl.scala b/src/col/vct/col/ast/family/seqguard/UnpointedGuardImpl.scala new file mode 100644 index 0000000000..2ce7c839af --- /dev/null +++ b/src/col/vct/col/ast/family/seqguard/UnpointedGuardImpl.scala @@ -0,0 +1,7 @@ +package vct.col.ast.family.seqguard + +import vct.col.ast.UnpointedGuard + +trait UnpointedGuardImpl[G] { this: UnpointedGuard[G] => + def endpointOpt = None +} diff --git a/src/col/vct/col/ast/statement/veymont/SeqBranchImpl.scala b/src/col/vct/col/ast/statement/veymont/SeqBranchImpl.scala index 8a1b70f6c7..93f26e2f2f 100644 --- a/src/col/vct/col/ast/statement/veymont/SeqBranchImpl.scala +++ b/src/col/vct/col/ast/statement/veymont/SeqBranchImpl.scala @@ -1,10 +1,12 @@ package vct.col.ast.statement.veymont -import vct.col.ast.{Endpoint, EndpointGuard, SeqBranch, UnpointedGuard} +import vct.col.ast.{Access, Communicate, Endpoint, EndpointGuard, EndpointName, SeqAssign, SeqBranch, UnpointedGuard} import vct.col.ast.statement.StatementImpl import vct.col.check.{CheckContext, CheckError, SeqProgParticipant} import vct.col.ref.Ref +import scala.collection.immutable.ListSet + trait SeqBranchImpl[G] extends StatementImpl[G] { this: SeqBranch[G] => def hasUnpointed: Boolean = guards.exists { case _: UnpointedGuard[G] => true; case _ => false } def explicitParticipants: Seq[Endpoint[G]] = guards.collect { case EndpointGuard(Ref(endpoint), condition) => endpoint } @@ -35,4 +37,12 @@ trait SeqBranchImpl[G] extends StatementImpl[G] { this: SeqBranch[G] => } } + // All participants that concretely participate in the branch by being named explicitly through the condition, + // an assignment, or a communicate. + def participants: Set[Endpoint[G]] = + ListSet.from(subnodes.collect { + case Communicate(Access(EndpointName(Ref(receiver)), _), Access(EndpointName(Ref(sender)), _)) => Seq(receiver, sender) + case SeqAssign(Ref(receiver), _, _) => Seq(receiver) + case branch: SeqBranch[G] => branch.explicitParticipants + }.flatten) } diff --git a/src/col/vct/col/check/Check.scala b/src/col/vct/col/check/Check.scala index 6da3551509..502b398dde 100644 --- a/src/col/vct/col/check/Check.scala +++ b/src/col/vct/col/check/Check.scala @@ -65,7 +65,7 @@ sealed trait CheckError { case SeqProgInstanceMethodNonVoid(m) => Seq(context(m, "An instance method in a `seq_prog` must have return type `void`.")) case SeqProgInvocation(s) => Seq(context(s, "Only invocations on `this` and endpoints are allowed.")) case SeqProgReceivingEndpoint(e) => Seq(context(e, s"Can only refer to the receiving endpoint of this statement.")) - case SeqProgParticipant(s) => Seq(context(s, s"This branch lets an endpoint participant which has been excluded by earlier branches.")) + case SeqProgParticipant(s) => Seq(context(s, s"This branch lets an endpoint participate which has been excluded by earlier branches.")) }).mkString(Origin.BOLD_HR, Origin.HR, Origin.BOLD_HR) def subcode: String diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index 8f9cfefd4f..67237855dc 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -24,7 +24,7 @@ import vct.resources.Resources import vct.result.VerificationError.SystemError import vct.rewrite.{EncodeResourceValues, ExplicitResourceValues, HeapVariableToRef} import vct.rewrite.lang.ReplaceSYCLTypes -import vct.rewrite.veymont.{EncodeSeqBranchUnanimity, EncodeSeqProg, GenerateSeqProgPermissions, SplitSeqGuards} +import vct.rewrite.veymont.{DeduplicateSeqGuards, EncodeSeqBranchUnanimity, EncodeSeqProg, GenerateSeqProgPermissions, RemoveUnpointedGuard, SplitSeqGuards} object Transformation { case class TransformationCheckError(pass: RewriterBuilder, errors: Seq[(Program[_], CheckError)]) extends SystemError { @@ -195,6 +195,8 @@ case class SilverTransformation // VeyMont sequential program encoding GenerateSeqProgPermissions, SplitSeqGuards, + RemoveUnpointedGuard, + DeduplicateSeqGuards, EncodeSeqBranchUnanimity, EncodeSeqProg, diff --git a/src/rewrite/vct/rewrite/veymont/DeduplicateSeqGuards.scala b/src/rewrite/vct/rewrite/veymont/DeduplicateSeqGuards.scala new file mode 100644 index 0000000000..2f4330c839 --- /dev/null +++ b/src/rewrite/vct/rewrite/veymont/DeduplicateSeqGuards.scala @@ -0,0 +1,41 @@ +package vct.rewrite.veymont + +import hre.util.ScopedStack +import vct.col.ast.RewriteHelpers._ +import vct.col.ast._ +import vct.col.origin.{DiagnosticOrigin, Origin} +import vct.col.ref.Ref +import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} +import vct.col.util.AstBuildHelpers._ +import vct.result.VerificationError.UserError +import vct.rewrite.veymont.SplitSeqGuards.MultipleEndpoints + +import scala.collection.mutable + +object DeduplicateSeqGuards extends RewriterBuilder { + override def key: String = "deduplicateSeqGuards" + override def desc: String = "Deduplicates SeqGuard nodes with syntactically identical endpoints" +} + +case class DeduplicateSeqGuards[Pre <: Generation]() extends Rewriter[Pre] { + override def dispatch(statement: Statement[Pre]): Statement[Post] = statement match { + case branch: SeqBranch[Pre] => + val guards: Seq[EndpointGuard[Pre]] = branch.guards.map { + case guard: EndpointGuard[Pre] => guard + case guard: UnpointedGuard[Pre] => ??? // Excluded by RemoveUnpointedGuard + } + branch.rewrite(guards = dedup(guards)) + + case _ => rewriteDefault(statement) + } + + def dedup(guards: Seq[EndpointGuard[Pre]]): Seq[EndpointGuard[Post]] = { + val m: mutable.LinkedHashMap[Endpoint[Pre], Seq[Expr[Pre]]] = mutable.LinkedHashMap() + guards.foreach { guard => + m.updateWith(guard.endpoint.decl)(exprs => Some(exprs.getOrElse(Nil) :+ guard.condition)) + } + m.iterator.map { case (endpoint, exprs) => + EndpointGuard[Post](succ(endpoint), foldAnd(exprs.map(dispatch))(DiagnosticOrigin))(DiagnosticOrigin) + }.toSeq + } +} diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala b/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala index 81ba968da2..4fa5263963 100644 --- a/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala @@ -8,7 +8,7 @@ import vct.col.util.SuccessionMap import vct.result.VerificationError.UserError object EncodeSeqBranchUnanimity extends RewriterBuilder { - override def key: String = "encodeBranchUnanimity" + override def key: String = "encodeSeqBranchUnanimity" override def desc: String = "Encodes the branch unanimity requirement imposed by VeyMont on branches and loops in seq_program nodes." case class AddVeyMontConditionError(node : Node[_], msg: String) extends UserError { diff --git a/src/rewrite/vct/rewrite/veymont/RemoveUnpointedGuard.scala b/src/rewrite/vct/rewrite/veymont/RemoveUnpointedGuard.scala new file mode 100644 index 0000000000..b7d15dd058 --- /dev/null +++ b/src/rewrite/vct/rewrite/veymont/RemoveUnpointedGuard.scala @@ -0,0 +1,54 @@ +package vct.rewrite.veymont + +import hre.util.ScopedStack +import vct.col.ast.RewriteHelpers._ +import vct.col.ast._ +import vct.col.origin.{DiagnosticOrigin, Origin} +import vct.col.ref.Ref +import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} +import vct.col.util.AstBuildHelpers._ +import vct.result.VerificationError.UserError +import vct.rewrite.veymont.SplitSeqGuards.MultipleEndpoints + +import scala.collection.immutable.ListSet + +object RemoveUnpointedGuard extends RewriterBuilder { + override def key: String = "removeUnpointedGuard" + override def desc: String = "Removes unpointed guard by duplicating the condition to all guards currently participating" +} + +case class RemoveUnpointedGuard[Pre <: Generation]() extends Rewriter[Pre] { + val currentParticipants: ScopedStack[ListSet[Endpoint[Pre]]] = ScopedStack() + + override def dispatch(decl: Declaration[Pre]): Unit = decl match { + case prog: SeqProg[Pre] => currentParticipants.having(ListSet.from(prog.endpoints)) { + rewriteDefault(prog) + } + + case decl => rewriteDefault(decl) + } + + override def dispatch(statement: Statement[Pre]): Statement[Post] = statement match { + case branch@SeqBranch(guards, yes, no) => + val newParticipants = if (branch.hasUnpointed) { + currentParticipants.top + } else { + ListSet.from(branch.participants) + } + currentParticipants.having(newParticipants) { + SeqBranch( + branch.guards.flatMap(rewriteGuard), + dispatch(yes), + no.map(dispatch) + )(branch.o) + } + case statement => rewriteDefault(statement) + } + + def rewriteGuard(guard: SeqGuard[Pre]): Seq[SeqGuard[Post]] = guard match { + case guard: EndpointGuard[Pre] => Seq(guard.rewriteDefault()) + case UnpointedGuard(expr) => currentParticipants.top.map { endpoint => + EndpointGuard[Post](succ(endpoint), dispatch(expr))(guard.o) + }.toSeq + } +} diff --git a/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala b/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala index 26a207b24e..b4a424275b 100644 --- a/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala +++ b/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala @@ -1,26 +1,51 @@ package vct.rewrite.veymont import hre.util.ScopedStack +import vct.col.ast.RewriteHelpers._ import vct.col.ast._ -import vct.col.origin.Origin +import vct.col.check.SeqProgParticipant +import vct.col.origin.{DiagnosticOrigin, Origin} import vct.col.ref.Ref import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} import vct.col.util.AstBuildHelpers._ import vct.result.VerificationError.UserError -import vct.rewrite.veymont.SplitSeqGuards.MultipleEndpoints +import vct.rewrite.veymont.SplitSeqGuards.{MultipleEndpoints, SeqProgParticipantErrors} + +import scala.collection.immutable.ListSet +import scala.collection.mutable object SplitSeqGuards extends RewriterBuilder { - override def key: String = "inferSeqGuards" + override def key: String = "splitSeqGuards" override def desc: String = "Lifts conditions in loops and conditionals into the SeqGuard AST node, stratifying the condition per endpoint." case class MultipleEndpoints(e: Expr[_]) extends UserError { override def code: String = "multipleEndpoints" override def text: String = e.o.messageInContext("This expression references multiple endpoints, but that is not yet supported.") } + + case class SeqProgParticipantErrors(es: Seq[SeqProgParticipant]) extends UserError { + override def code: String = "seqProgParticipantErrors" + override def text: String = es.map { + case err: SeqProgParticipant => err.message { (n, m) => n.o.bareMessageInContext(m) } + }.mkString("\n") + } } case class SplitSeqGuards[Pre <: Generation]() extends Rewriter[Pre] { val currentProg: ScopedStack[SeqProg[Pre]] = ScopedStack() + val currentParticipants: ScopedStack[ListSet[Pre]] = ScopedStack() + + override def dispatch(prog: Program[Pre]): Program[Post] = { + val newProg = prog.rewrite() + val errors = newProg.check + val seqBranchErrors = errors.collect { + case err: SeqProgParticipant => err + } + if (errors.nonEmpty && errors.length == seqBranchErrors.length) { + throw SeqProgParticipantErrors(seqBranchErrors) + } + newProg + } override def dispatch(decl: Declaration[Pre]): Unit = decl match { case prog: SeqProg[Pre] => @@ -45,22 +70,28 @@ case class SplitSeqGuards[Pre <: Generation]() extends Rewriter[Pre] { case Seq((e, s)) => SeqBranch(inferSeqGuard(e), dispatch(s), None) case (e, s) +: (otherYes +: branches) => SeqBranch(inferSeqGuard(e), dispatch(s), Some(unfoldBranch(otherYes +: branches))) + case _ => ??? } // Infer guard conditions based on the classic syntactical restrictions - endpoint dereferences determine which // endpoint is evaluating the expression. - def inferSeqGuard(e: Expr[Pre]): Seq[SeqGuard[Post]] = - unfoldStar(e).map(target) + def inferSeqGuard(e: Expr[Pre]): Seq[SeqGuard[Post]] = { + val exprs = unfoldStar(e) + val pointed = exprs.map(point) + pointed.map { + case (Some(endpoint), expr) => EndpointGuard[Post](succ(endpoint), dispatch(expr))(DiagnosticOrigin) + case (None, expr) => UnpointedGuard(dispatch(expr))(DiagnosticOrigin) + } + } - // This method makes an experssion targeted, in the sense that: if it is syntactically obvious the expression is to be - // executed in the context of a certain endpoint, it is marked as a SeqGuard belonging to that endpoint - def target(e: Expr[Pre]): SeqGuard[Post] = { + // "Points" an expression in the direction of an endpoint if possible + def point(e: Expr[Pre]): (Option[Endpoint[Pre]], Expr[Pre]) = { val endpoints: Seq[Endpoint[Pre]] = e.collect { case Deref(EndpointUse(Ref(endpoint)), _) => endpoint } endpoints match { case Seq(endpoint) => // expr is totally in context of one endpoint and whatever else is in scope - EndpointGuard[Post](succ(endpoint), dispatch(e))(e.o) - case Seq() => UnpointedGuard(dispatch(e))(e.o) // Expr doesn't use any endpoints - probably fine? + (Some(endpoint), e) + case Seq() => (None, e) case _ => throw MultipleEndpoints(e) // Expr uses multiple endpoints - for now we should disallow that. } } diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index 45fb62113a..1a6b8b752f 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -269,12 +269,32 @@ class TechnicalVeyMontSpec extends VercorsSpec { pure int f() = 3; + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + + seq_run { + if (alice.x == 0 && f() == 3) { + // Alice might go here, bob will definitely, because of the second expression: error + } + } + } + """ + + vercors should verify using silicon in "If there is only one endpoint, the conditions don't have to agree, as there is only one endpoint" pvl + """ + class Storage { + int x; + } + + pure int f() = 3; + seq_program Example() { endpoint alice = Storage(); seq_run { if (alice.x == 0 && f() == 3) { - // Alice might go here, will definitely, because of the second expression: error + // Alice might go here, bob will definitely, because of the second expression: error } } } @@ -299,3 +319,81 @@ class TechnicalVeyMontSpec extends VercorsSpec { } """ } + +class TechnicalVeyMontSpec2 extends VercorsSpec { + vercors should fail withCode "seqProgParticipantErrors" using silicon in "Parts of condition in branch have to agree inside seqprog" pvl + """ + class Storage { + int x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + + seq_run { + if (alice.x == 0 && bob.x == 0) { + // Alice might go here, bob might not: error + } + } + } + """ + + vercors should fail withCode "??? error missing" using silicon in "Parts of condition in branch have to agree inside seqprog, including conditions for all endpoints" pvl + """ + class Storage { + int x; + } + + pure int f() = 3; + + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + + seq_run { + if (alice.x == 0 && f() == 3) { + // Alice might go here, bob will definitely, because of the second expression: error + } + } + } + """ + + vercors should verify using silicon in "If there is only one endpoint, the conditions don't have to agree, as there is only one endpoint" pvl + """ + class Storage { + int x; + } + + pure int f() = 3; + + seq_program Example() { + endpoint alice = Storage(); + + seq_run { + if (alice.x == 0 && f() == 3) { + // Alice might go here, bob will definitely, because of the second expression: error + } + } + } + """ + + vercors should error withCode "seqProgParticipantErrors" in "`if` cannot depend on bob, inside an `if` depending on alice" pvl + """ + class Storage { + int x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + + seq_run { + if (alice.x == 0) { + if (bob.x == 0) { + // Error + } + } + } + } + """ + +} \ No newline at end of file From 5bd1bbcf17baa2434027233f6598be19abe64984 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Tue, 14 Nov 2023 17:57:40 +0100 Subject: [PATCH 04/32] Now also with pretty first-class seqbranch errors --- src/col/vct/col/ast/Node.scala | 5 +- .../vct/col/ast/statement/StatementImpl.scala | 4 +- src/col/vct/col/origin/Blame.scala | 15 ++++ .../vct/col/typerules/CoercingRewriter.scala | 2 + .../vct/parsers/transform/PVLToCol.scala | 2 +- .../ResolveExpressionSideEffects.scala | 2 + .../vct/rewrite/lang/LangPVLToCol.scala | 7 ++ .../vct/rewrite/lang/LangSpecificToCol.scala | 2 + .../vct/rewrite/lang/LangVeyMontToCol.scala | 52 +++++++----- .../veymont/DeduplicateSeqGuards.scala | 2 +- .../veymont/EncodeSeqBranchUnanimity.scala | 12 ++- .../veymont/RemoveUnpointedGuard.scala | 10 +-- .../vct/rewrite/veymont/SplitSeqGuards.scala | 16 ++-- .../examples/TechnicalVeyMontSpec.scala | 84 +------------------ .../test/integration/helper/VercorsSpec.scala | 2 +- 15 files changed, 91 insertions(+), 126 deletions(-) diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index 0284023108..2ae76cb6e0 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -156,6 +156,8 @@ final case class IterVariable[G](variable: Variable[G], from: Expr[G], to: Expr[ sealed trait Statement[G] extends NodeFamily[G] with StatementImpl[G] +final case class PVLBranch[G](branches: Seq[(Expr[G], Statement[G])])(val blame: Blame[FrontendIfFailure])(implicit val o: Origin) extends Statement[G] + sealed trait NonExecutableStatement[G] extends Statement[G] with NonExecutableStatementImpl[G] final case class LocalDecl[G](local: Variable[G])(implicit val o: Origin) extends NonExecutableStatement[G] with LocalDeclImpl[G] final case class SpecIgnoreStart[G]()(implicit val o: Origin) extends NonExecutableStatement[G] with SpecIgnoreStartImpl[G] @@ -1274,10 +1276,11 @@ final case class Communicate[G](receiver: Access[G], sender: Access[G])(implicit final case class SeqAssign[G](receiver: Ref[G, Endpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(implicit val o: Origin) extends Statement[G] with SeqAssignImpl[G] final case class EndpointUse[G](ref: Ref[G, Endpoint[G]])(implicit val o: Origin) extends Expr[G] with EndpointUseImpl[G] +final case class UnresolvedSeqBranch[G](branches: Seq[(Expr[G], Statement[G])])(val blame: Blame[SeqBranchFailure])(implicit val o: Origin) extends Statement[G] sealed trait SeqGuard[G] extends NodeFamily[G] with SeqGuardImpl[G] final case class EndpointGuard[G](endpoint: Ref[G, Endpoint[G]], condition: Expr[G])(implicit val o: Origin) extends SeqGuard[G] with EndpointGuardImpl[G] final case class UnpointedGuard[G](condition: Expr[G])(implicit val o: Origin) extends SeqGuard[G] with UnpointedGuardImpl[G] -final case class SeqBranch[G](guards: Seq[SeqGuard[G]], yes: Statement[G], no: Option[Statement[G]])(implicit val o: Origin) extends Statement[G] with SeqBranchImpl[G] +final case class SeqBranch[G](guards: Seq[SeqGuard[G]], yes: Statement[G], no: Option[Statement[G]])(val blame: Blame[SeqBranchFailure])(implicit val o: Origin) extends Statement[G] with SeqBranchImpl[G] final case class VeyMontAssignExpression[G](endpoint: Ref[G, Endpoint[G]], assign: Statement[G])(implicit val o: Origin) extends Statement[G] with VeyMontAssignExpressionImpl[G] final case class CommunicateX[G](receiver: Ref[G, Endpoint[G]], sender: Ref[G, Endpoint[G]], chanType: Type[G], assign: Statement[G])(implicit val o: Origin) extends Statement[G] with CommunicateXImpl[G] diff --git a/src/col/vct/col/ast/statement/StatementImpl.scala b/src/col/vct/col/ast/statement/StatementImpl.scala index 4331931efc..653658feb6 100644 --- a/src/col/vct/col/ast/statement/StatementImpl.scala +++ b/src/col/vct/col/ast/statement/StatementImpl.scala @@ -26,7 +26,9 @@ trait StatementImpl[G] extends NodeFamilyImpl[G] { this: Statement[G] => _: Block[G] | _: Eval[G] | _: Assert[G] | - _: SeqBranch[G] => Seq() + _: SeqBranch[G] | + _: UnresolvedSeqBranch[G] + => Seq() case _ => Seq(SeqProgStatement(this)) } }) diff --git a/src/col/vct/col/origin/Blame.scala b/src/col/vct/col/origin/Blame.scala index bc9fbd831b..9ddb347643 100644 --- a/src/col/vct/col/origin/Blame.scala +++ b/src/col/vct/col/origin/Blame.scala @@ -304,6 +304,21 @@ case class PlusProviderInvocationFailed(innerFailure: WithContractFailure) exten override def inlineDescWithSource(node: String, failure: String): String = innerFailure.inlineDescWithSource(node, failure) } +sealed trait FrontendIfFailure extends VerificationFailure +sealed trait SeqBranchFailure extends FrontendIfFailure + +case class BranchUnanimityFailed(guard1: Node[_], guard2: Node[_]) extends SeqBranchFailure { + override def code: String = "branchNotUnanimous" + + override def desc: String = Origin.messagesInContext(Seq( + (guard1.o, "This condition..."), + (guard2.o, "...should agree with this condition, but this might not be the case") + )) + + override def position: String = guard1.o.getShortPositionOrElse() + override def inlineDesc: String = "Two conditions in this branch might disagree." +} + sealed trait DerefInsufficientPermission extends FrontendDerefError case class InsufficientPermission(node: HeapDeref[_]) extends DerefInsufficientPermission with NodeVerificationFailure { override def code: String = "perm" diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index e004f82d4f..bfba846fe0 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -1694,6 +1694,8 @@ abstract class CoercingRewriter[Pre <: Generation]() extends AbstractRewriter[Pr throw err } case s: SeqBranch[Pre] => s + case branch@UnresolvedSeqBranch(branches) => UnresolvedSeqBranch(branches.map { case (cond, effect) => (bool(cond), effect) })(branch.blame) + case branch@PVLBranch(branches) => PVLBranch(branches.map { case (cond, effect) => (bool(cond), effect) })(branch.blame) } } diff --git a/src/parsers/vct/parsers/transform/PVLToCol.scala b/src/parsers/vct/parsers/transform/PVLToCol.scala index c4550b0d1d..f7c3038433 100644 --- a/src/parsers/vct/parsers/transform/PVLToCol.scala +++ b/src/parsers/vct/parsers/transform/PVLToCol.scala @@ -301,7 +301,7 @@ case class PVLToCol[G](override val baseOrigin: Origin, case PvlJoin(_, obj, _) => Join(convert(obj))(blame(stat)) case PvlValStatement(inner) => convert(inner) case PvlIf(_, _, cond, _, body, None) => - Branch(Seq((convert(cond), convert(body)))) + PVLBranch(Seq((convert(cond), convert(body))))(blame(stat)) case PvlIf(_, _, cond, _, body, Some(ElseBlock0(_, otherwise))) => Branch(Seq( (convert(cond), convert(body)), diff --git a/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala b/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala index 762b39123a..51b85c0e9b 100644 --- a/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala +++ b/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala @@ -351,6 +351,8 @@ case class ResolveExpressionSideEffects[Pre <: Generation]() extends Rewriter[Pr case comm: CommunicateX[Pre] => rewriteDefault(comm) case comm: PVLCommunicate[Pre] => rewriteDefault(comm) case comm: Communicate[Pre] => rewriteDefault(comm) + case _: PVLBranch[Pre] => throw ExtraNode + case _: UnresolvedSeqBranch[Pre] => throw ExtraNode case _: SeqBranch[Pre] => throw ExtraNode case _: CStatement[Pre] => throw ExtraNode case _: CPPStatement[Pre] => throw ExtraNode diff --git a/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala b/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala index 35026b377f..6f40b59705 100644 --- a/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala @@ -131,4 +131,11 @@ case class LangPVLToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends L yields.map { case (e, Ref(v)) => (rw.dispatch(e), rw.succ(v)) })(inv.blame) } } + + def branch(branch: PVLBranch[Pre]): Statement[Post] = + if (rw.veymont.currentProg.nonEmpty) { + rw.veymont.rewriteBranch(branch) + } else { + Branch(branch.branches.map { case (e, s) => (rw.dispatch(e), rw.dispatch(s)) })(branch.o) + } } diff --git a/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala b/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala index c2ac938b99..04fae5b856 100644 --- a/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala @@ -155,6 +155,8 @@ case class LangSpecificToCol[Pre <: Generation]() extends Rewriter[Pre] with Laz scanScope(body) }._1) + case branch: PVLBranch[Pre] => pvl.branch(branch) + case JavaLocalDeclarationStatement(locals: JavaLocalDeclaration[Pre]) => java.initLocal(locals) case CDeclarationStatement(decl) => c.rewriteLocal(decl) diff --git a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala index 87f34cfe53..10787ea43b 100644 --- a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala @@ -1,6 +1,7 @@ package vct.rewrite.lang import com.typesafe.scalalogging.LazyLogging +import hre.util.ScopedStack import vct.col.ast._ import vct.col.ast.RewriteHelpers._ import vct.col.origin.{DiagnosticOrigin, Origin} @@ -34,6 +35,8 @@ case class LangVeyMontToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) exten val seqProgSucc: SuccessionMap[PVLSeqProg[Pre], SeqProg[Post]] = SuccessionMap() val endpointSucc: SuccessionMap[PVLEndpoint[Pre], Endpoint[Post]] = SuccessionMap() + val currentProg: ScopedStack[PVLSeqProg[Pre]] = ScopedStack() + def rewriteCommunicate(comm: PVLCommunicate[Pre]): Communicate[Post] = Communicate(rewriteAccess(comm.receiver), rewriteAccess(comm.sender))(comm.o) @@ -56,28 +59,30 @@ case class LangVeyMontToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) exten def rewriteSeqProg(prog: PVLSeqProg[Pre]): Unit = { implicit val o: Origin = prog.o rw.currentThis.having(ThisSeqProg[Post](seqProgSucc.ref(prog))) { - seqProgSucc(prog) = rw.globalDeclarations.declare( - new SeqProg( - rw.dispatch(prog.contract), - rw.variables.collect(prog.args.map(rw.dispatch(_)))._1, - rw.endpoints.collect( - prog.declarations.foreach { - case endpoint: PVLEndpoint[Pre] => rewriteEndpoint(endpoint) - case _ => - }, - )._1, - prog.declarations.collectFirst { - case run: PVLSeqRun[Pre] => rewriteRun(run) - }.getOrElse(throw NoRunMethod(prog)), - rw.classDeclarations.collect( - prog.declarations.foreach { - case _: PVLSeqRun[Pre] => - case _: PVLEndpoint[Pre] => - case decl => rw.dispatch(decl) - } - )._1 - )(prog.o) - ) + currentProg.having(prog) { + seqProgSucc(prog) = rw.globalDeclarations.declare( + new SeqProg( + rw.dispatch(prog.contract), + rw.variables.collect(prog.args.map(rw.dispatch(_)))._1, + rw.endpoints.collect( + prog.declarations.foreach { + case endpoint: PVLEndpoint[Pre] => rewriteEndpoint(endpoint) + case _ => + }, + )._1, + prog.declarations.collectFirst { + case run: PVLSeqRun[Pre] => rewriteRun(run) + }.getOrElse(throw NoRunMethod(prog)), + rw.classDeclarations.collect( + prog.declarations.foreach { + case _: PVLSeqRun[Pre] => + case _: PVLEndpoint[Pre] => + case decl => rw.dispatch(decl) + } + )._1 + )(prog.o) + ) + } } } @@ -91,4 +96,7 @@ case class LangVeyMontToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) exten def rewriteParAssign(assign: PVLSeqAssign[Pre]): SeqAssign[Post] = SeqAssign[Post](endpointSucc.ref(assign.receiver.decl), rw.succ(assign.field.decl), rw.dispatch(assign.value))(assign.o) + + def rewriteBranch(branch: PVLBranch[Pre]): UnresolvedSeqBranch[Post] = + UnresolvedSeqBranch(branch.branches.map { case (e, s) => (rw.dispatch(e), rw.dispatch(s)) })(branch.blame)(branch.o) } diff --git a/src/rewrite/vct/rewrite/veymont/DeduplicateSeqGuards.scala b/src/rewrite/vct/rewrite/veymont/DeduplicateSeqGuards.scala index 2f4330c839..96b79bf688 100644 --- a/src/rewrite/vct/rewrite/veymont/DeduplicateSeqGuards.scala +++ b/src/rewrite/vct/rewrite/veymont/DeduplicateSeqGuards.scala @@ -35,7 +35,7 @@ case class DeduplicateSeqGuards[Pre <: Generation]() extends Rewriter[Pre] { m.updateWith(guard.endpoint.decl)(exprs => Some(exprs.getOrElse(Nil) :+ guard.condition)) } m.iterator.map { case (endpoint, exprs) => - EndpointGuard[Post](succ(endpoint), foldAnd(exprs.map(dispatch))(DiagnosticOrigin))(DiagnosticOrigin) + EndpointGuard[Post](succ(endpoint), foldAnd(exprs.map(dispatch))(DiagnosticOrigin))(exprs.head.o) }.toSeq } } diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala b/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala index 4fa5263963..41587f67c1 100644 --- a/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala @@ -1,11 +1,12 @@ package vct.rewrite.veymont import vct.col.ast._ -import vct.col.origin.PanicBlame +import vct.col.origin.{AssertFailed, Blame, BranchUnanimityFailed} import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} import vct.col.util.AstBuildHelpers._ import vct.col.util.SuccessionMap import vct.result.VerificationError.UserError +import vct.rewrite.veymont.EncodeSeqBranchUnanimity.ForwardBranchUnanimity object EncodeSeqBranchUnanimity extends RewriterBuilder { override def key: String = "encodeSeqBranchUnanimity" @@ -15,6 +16,11 @@ object EncodeSeqBranchUnanimity extends RewriterBuilder { override def code: String = "addVeyMontConditionError" override def text: String = node.o.messageInContext(msg) } + + case class ForwardBranchUnanimity(branch: SeqBranch[_], c1: SeqGuard[_], c2: SeqGuard[_]) extends Blame[AssertFailed] { + override def blame(error: AssertFailed): Unit = + branch.blame.blame(BranchUnanimityFailed(c1, c2)) + } } case class EncodeSeqBranchUnanimity[Pre <: Generation]() extends Rewriter[Pre] { @@ -22,7 +28,7 @@ case class EncodeSeqBranchUnanimity[Pre <: Generation]() extends Rewriter[Pre] { val guardSucc = SuccessionMap[SeqGuard[Pre], Variable[Post]]() override def dispatch(statement: Statement[Pre]): Statement[Post] = statement match { - case SeqBranch(guards, yes, no) => + case branch@SeqBranch(guards, yes, no) => implicit val o = statement.o /* for each c in conds: @@ -43,7 +49,7 @@ case class EncodeSeqBranchUnanimity[Pre <: Generation]() extends Rewriter[Pre] { val assertions: Seq[Assert[Post]] = (0 until guards.length - 1).map { i => val c1 = guards(i) val c2 = guards(i + 1) - Assert(guardSucc(c1).get === guardSucc(c2).get)(PanicBlame("TODO: Implement failing assert")) + Assert(guardSucc(c1).get === guardSucc(c2).get)(ForwardBranchUnanimity(branch, c1, c2)) } val majorCond = new Variable[Post](TBool()) diff --git a/src/rewrite/vct/rewrite/veymont/RemoveUnpointedGuard.scala b/src/rewrite/vct/rewrite/veymont/RemoveUnpointedGuard.scala index b7d15dd058..4d70d10caa 100644 --- a/src/rewrite/vct/rewrite/veymont/RemoveUnpointedGuard.scala +++ b/src/rewrite/vct/rewrite/veymont/RemoveUnpointedGuard.scala @@ -3,7 +3,7 @@ package vct.rewrite.veymont import hre.util.ScopedStack import vct.col.ast.RewriteHelpers._ import vct.col.ast._ -import vct.col.origin.{DiagnosticOrigin, Origin} +import vct.col.origin.{Origin} import vct.col.ref.Ref import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} import vct.col.util.AstBuildHelpers._ @@ -29,18 +29,14 @@ case class RemoveUnpointedGuard[Pre <: Generation]() extends Rewriter[Pre] { } override def dispatch(statement: Statement[Pre]): Statement[Post] = statement match { - case branch@SeqBranch(guards, yes, no) => + case branch: SeqBranch[Pre] => val newParticipants = if (branch.hasUnpointed) { currentParticipants.top } else { ListSet.from(branch.participants) } currentParticipants.having(newParticipants) { - SeqBranch( - branch.guards.flatMap(rewriteGuard), - dispatch(yes), - no.map(dispatch) - )(branch.o) + branch.rewrite(guards = branch.guards.flatMap(rewriteGuard)) } case statement => rewriteDefault(statement) } diff --git a/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala b/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala index b4a424275b..fdd7874ce1 100644 --- a/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala +++ b/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala @@ -4,7 +4,7 @@ import hre.util.ScopedStack import vct.col.ast.RewriteHelpers._ import vct.col.ast._ import vct.col.check.SeqProgParticipant -import vct.col.origin.{DiagnosticOrigin, Origin} +import vct.col.origin.{Blame, Origin, SeqBranchFailure} import vct.col.ref.Ref import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} import vct.col.util.AstBuildHelpers._ @@ -56,9 +56,9 @@ case class SplitSeqGuards[Pre <: Generation]() extends Rewriter[Pre] { } override def dispatch(statement: Statement[Pre]): Statement[Post] = statement match { - case branch: Branch[Pre] if currentProg.nonEmpty => + case branch: UnresolvedSeqBranch[Pre] => assert(branch.branches.nonEmpty) - unfoldBranch(branch.branches)(branch.o) + unfoldBranch(branch.branches)(branch.blame, branch.o) case l: Loop[Pre] if currentProg.nonEmpty => ??? @@ -66,10 +66,10 @@ case class SplitSeqGuards[Pre <: Generation]() extends Rewriter[Pre] { case statement => rewriteDefault(statement) } - def unfoldBranch(branches: Seq[(Expr[Pre], Statement[Pre])])(implicit o: Origin): SeqBranch[Post] = branches match { - case Seq((e, s)) => SeqBranch(inferSeqGuard(e), dispatch(s), None) + def unfoldBranch(branches: Seq[(Expr[Pre], Statement[Pre])])(implicit blame: Blame[SeqBranchFailure], o: Origin): SeqBranch[Post] = branches match { + case Seq((e, s)) => SeqBranch(inferSeqGuard(e), dispatch(s), None)(blame) case (e, s) +: (otherYes +: branches) => - SeqBranch(inferSeqGuard(e), dispatch(s), Some(unfoldBranch(otherYes +: branches))) + SeqBranch(inferSeqGuard(e), dispatch(s), Some(unfoldBranch(otherYes +: branches)))(blame) case _ => ??? } @@ -79,8 +79,8 @@ case class SplitSeqGuards[Pre <: Generation]() extends Rewriter[Pre] { val exprs = unfoldStar(e) val pointed = exprs.map(point) pointed.map { - case (Some(endpoint), expr) => EndpointGuard[Post](succ(endpoint), dispatch(expr))(DiagnosticOrigin) - case (None, expr) => UnpointedGuard(dispatch(expr))(DiagnosticOrigin) + case (Some(endpoint), expr) => EndpointGuard[Post](succ(endpoint), dispatch(expr))(expr.o) + case (None, expr) => UnpointedGuard(dispatch(expr))(expr.o) } } diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index 1a6b8b752f..2eac7c79cd 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -244,7 +244,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { } """ - vercors should fail withCode "??? error missing" using silicon in "Parts of condition in branch have to agree inside seqprog" pvl + vercors should fail withCode "branchNotUnanimous" using silicon in "Parts of condition in branch have to agree inside seqprog" pvl """ class Storage { int x; @@ -261,7 +261,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { } """ - vercors should fail withCode "??? error missing" using silicon in "Parts of condition in branch have to agree inside seqprog, including conditions for all endpoints" pvl + vercors should fail withCode "branchNotUnanimous" using silicon in "Parts of condition in branch have to agree inside seqprog, including conditions for all endpoints" pvl """ class Storage { int x; @@ -300,85 +300,8 @@ class TechnicalVeyMontSpec extends VercorsSpec { } """ - vercors should error withCode "??? error missing" in "`if` cannot depend on bob, inside an `if` depending on alice" pvl - """ - class Storage { - int x; - } - seq_program Example() { - endpoint alice = Storage(); - endpoint bob = Storage(); - - seq_run { - if (alice.x == 0) { - if (bob.x == 0) { - // Error - } - } - } - } - """ -} - -class TechnicalVeyMontSpec2 extends VercorsSpec { - vercors should fail withCode "seqProgParticipantErrors" using silicon in "Parts of condition in branch have to agree inside seqprog" pvl - """ - class Storage { - int x; - } - seq_program Example() { - endpoint alice = Storage(); - endpoint bob = Storage(); - - seq_run { - if (alice.x == 0 && bob.x == 0) { - // Alice might go here, bob might not: error - } - } - } - """ - - vercors should fail withCode "??? error missing" using silicon in "Parts of condition in branch have to agree inside seqprog, including conditions for all endpoints" pvl - """ - class Storage { - int x; - } - - pure int f() = 3; - - seq_program Example() { - endpoint alice = Storage(); - endpoint bob = Storage(); - - seq_run { - if (alice.x == 0 && f() == 3) { - // Alice might go here, bob will definitely, because of the second expression: error - } - } - } - """ - - vercors should verify using silicon in "If there is only one endpoint, the conditions don't have to agree, as there is only one endpoint" pvl - """ - class Storage { - int x; - } - - pure int f() = 3; - - seq_program Example() { - endpoint alice = Storage(); - - seq_run { - if (alice.x == 0 && f() == 3) { - // Alice might go here, bob will definitely, because of the second expression: error - } - } - } - """ - vercors should error withCode "seqProgParticipantErrors" in "`if` cannot depend on bob, inside an `if` depending on alice" pvl - """ + """ class Storage { int x; } @@ -395,5 +318,4 @@ class TechnicalVeyMontSpec2 extends VercorsSpec { } } """ - } \ No newline at end of file diff --git a/test/main/vct/test/integration/helper/VercorsSpec.scala b/test/main/vct/test/integration/helper/VercorsSpec.scala index ad709c05d3..30a5d1065f 100644 --- a/test/main/vct/test/integration/helper/VercorsSpec.scala +++ b/test/main/vct/test/integration/helper/VercorsSpec.scala @@ -134,7 +134,7 @@ abstract class VercorsSpec extends AnyFlatSpec { println(err) fail(s"Expected the test to fail with code $code, but it crashed with the above error instead.") case Right((Nil, _)) => - fail("Expected the test to fail with code $code, but it passed instead.") + fail(s"Expected the test to fail with code $code, but it passed instead.") case Right((fails, _)) => fails.filterNot(_.code == code) match { case Nil => // success case fails => From b34999e45fe46da532ffbebe01fa6aab354d18f4 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 15 Nov 2023 09:31:35 +0100 Subject: [PATCH 05/32] Implement participant checking for assign and communicate --- .../statement/veymont/CommunicateImpl.scala | 12 ++++++- .../ast/statement/veymont/SeqAssignImpl.scala | 6 +++- src/col/vct/col/check/Check.scala | 4 +-- .../examples/TechnicalVeyMontSpec.scala | 34 +++++++++++++++++++ 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/col/vct/col/ast/statement/veymont/CommunicateImpl.scala b/src/col/vct/col/ast/statement/veymont/CommunicateImpl.scala index db1fc99727..fd55e6c63d 100644 --- a/src/col/vct/col/ast/statement/veymont/CommunicateImpl.scala +++ b/src/col/vct/col/ast/statement/veymont/CommunicateImpl.scala @@ -1,9 +1,19 @@ package vct.col.ast.statement.veymont -import vct.col.ast.Communicate +import vct.col.ast.{Access, Communicate, EndpointName} +import vct.col.check.{CheckContext, CheckError, SeqProgParticipant} import vct.col.print.{Ctx, Doc, Text} +import vct.col.ref.Ref trait CommunicateImpl[G] { this: Communicate[G] => override def layout(implicit ctx: Ctx): Doc = Text("communicate") <+> receiver.show <+> "<-" <+> sender.show <> ";" + + override def check(context: CheckContext[G]): Seq[CheckError] = this match { + case Communicate(Access(name@EndpointName(Ref(receiver)), _), _) if !context.currentParticipatingEndpoints.get.contains(receiver) => + Seq(SeqProgParticipant(name)) + case Communicate(_, Access(name@EndpointName(Ref(sender)), _)) if !context.currentParticipatingEndpoints.get.contains(sender) => + Seq(SeqProgParticipant(name)) + case _ => Nil + } } diff --git a/src/col/vct/col/ast/statement/veymont/SeqAssignImpl.scala b/src/col/vct/col/ast/statement/veymont/SeqAssignImpl.scala index 2c4feeb5b4..2379fb63e0 100644 --- a/src/col/vct/col/ast/statement/veymont/SeqAssignImpl.scala +++ b/src/col/vct/col/ast/statement/veymont/SeqAssignImpl.scala @@ -2,7 +2,7 @@ package vct.col.ast.statement.veymont import vct.col.ast.SeqAssign import vct.col.ast.statement.StatementImpl -import vct.col.check.{CheckContext, CheckError} +import vct.col.check.{CheckContext, CheckError, SeqProgParticipant} import vct.col.print.{Ctx, Doc, Group, Text} trait SeqAssignImpl[G] extends StatementImpl[G] { this: SeqAssign[G] => @@ -13,4 +13,8 @@ trait SeqAssignImpl[G] extends StatementImpl[G] { this: SeqAssign[G] => override def layout(implicit ctx: Ctx): Doc = Group(Text(receiver.decl.o.getPreferredNameOrElse()) <> "." <> field.decl.o.getPreferredNameOrElse() <+> ":=" <+> value.show) + override def check(context: CheckContext[G]): Seq[CheckError] = + if (!context.currentParticipatingEndpoints.get.contains(this.receiver.decl)) + Seq(SeqProgParticipant(this)) + else Nil } diff --git a/src/col/vct/col/check/Check.scala b/src/col/vct/col/check/Check.scala index 502b398dde..ca2f43ac6d 100644 --- a/src/col/vct/col/check/Check.scala +++ b/src/col/vct/col/check/Check.scala @@ -65,7 +65,7 @@ sealed trait CheckError { case SeqProgInstanceMethodNonVoid(m) => Seq(context(m, "An instance method in a `seq_prog` must have return type `void`.")) case SeqProgInvocation(s) => Seq(context(s, "Only invocations on `this` and endpoints are allowed.")) case SeqProgReceivingEndpoint(e) => Seq(context(e, s"Can only refer to the receiving endpoint of this statement.")) - case SeqProgParticipant(s) => Seq(context(s, s"This branch lets an endpoint participate which has been excluded by earlier branches.")) + case SeqProgParticipant(s) => Seq(context(s, s"An endpoint is used in this branch which is not allowed to participate at this point in the program because of earlier branches.")) }).mkString(Origin.BOLD_HR, Origin.HR, Origin.BOLD_HR) def subcode: String @@ -128,7 +128,7 @@ case class SeqProgInvocation(s: Statement[_]) extends CheckError { case class SeqProgReceivingEndpoint(e: Expr[_]) extends CheckError { val subcode = "seqProgReceivingEndpoint" } -case class SeqProgParticipant(s: SeqBranch[_]) extends CheckError { +case class SeqProgParticipant(s: Node[_]) extends CheckError { val subcode = "seqProgParticipant" } diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index 2eac7c79cd..f660e63a76 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -318,4 +318,38 @@ class TechnicalVeyMontSpec extends VercorsSpec { } } """ + + vercors should error withCode "seqProgParticipantErrors" in "If alice branches, bob cannot communicate" pvl + """ + class Storage { + int x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + + seq_run { + if (alice.x == 0) { + communicate alice.x <- bob.x; + } + } + } + """ + + vercors should error withCode "seqProgParticipantErrors" in "If alice branches, bob cannot assign" pvl + """ + class Storage { + int x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + + seq_run { + if (alice.x == 0) { + bob.x := 3; + } + } + } + """ } \ No newline at end of file From 61ea2c61a808f95b8c4e5321cddb2e635ff2a41c Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 15 Nov 2023 09:37:36 +0100 Subject: [PATCH 06/32] Rename pass --- src/main/vct/main/stages/Transformation.scala | 4 ++-- ...emoveUnpointedGuard.scala => EncodeUnpointedGuard.scala} | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/rewrite/vct/rewrite/veymont/{RemoveUnpointedGuard.scala => EncodeUnpointedGuard.scala} (90%) diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index 67237855dc..9048d89c8c 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -24,7 +24,7 @@ import vct.resources.Resources import vct.result.VerificationError.SystemError import vct.rewrite.{EncodeResourceValues, ExplicitResourceValues, HeapVariableToRef} import vct.rewrite.lang.ReplaceSYCLTypes -import vct.rewrite.veymont.{DeduplicateSeqGuards, EncodeSeqBranchUnanimity, EncodeSeqProg, GenerateSeqProgPermissions, RemoveUnpointedGuard, SplitSeqGuards} +import vct.rewrite.veymont.{DeduplicateSeqGuards, EncodeSeqBranchUnanimity, EncodeSeqProg, GenerateSeqProgPermissions, EncodeUnpointedGuard, SplitSeqGuards} object Transformation { case class TransformationCheckError(pass: RewriterBuilder, errors: Seq[(Program[_], CheckError)]) extends SystemError { @@ -195,7 +195,7 @@ case class SilverTransformation // VeyMont sequential program encoding GenerateSeqProgPermissions, SplitSeqGuards, - RemoveUnpointedGuard, + EncodeUnpointedGuard, DeduplicateSeqGuards, EncodeSeqBranchUnanimity, EncodeSeqProg, diff --git a/src/rewrite/vct/rewrite/veymont/RemoveUnpointedGuard.scala b/src/rewrite/vct/rewrite/veymont/EncodeUnpointedGuard.scala similarity index 90% rename from src/rewrite/vct/rewrite/veymont/RemoveUnpointedGuard.scala rename to src/rewrite/vct/rewrite/veymont/EncodeUnpointedGuard.scala index 4d70d10caa..d3ed153f16 100644 --- a/src/rewrite/vct/rewrite/veymont/RemoveUnpointedGuard.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeUnpointedGuard.scala @@ -12,12 +12,12 @@ import vct.rewrite.veymont.SplitSeqGuards.MultipleEndpoints import scala.collection.immutable.ListSet -object RemoveUnpointedGuard extends RewriterBuilder { - override def key: String = "removeUnpointedGuard" +object EncodeUnpointedGuard extends RewriterBuilder { + override def key: String = "encodeUnpointedGuard" override def desc: String = "Removes unpointed guard by duplicating the condition to all guards currently participating" } -case class RemoveUnpointedGuard[Pre <: Generation]() extends Rewriter[Pre] { +case class EncodeUnpointedGuard[Pre <: Generation]() extends Rewriter[Pre] { val currentParticipants: ScopedStack[ListSet[Endpoint[Pre]]] = ScopedStack() override def dispatch(decl: Declaration[Pre]): Unit = decl match { From a629aa877091130c75200a1dd009d9cc1e849ea6 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 15 Nov 2023 10:05:24 +0100 Subject: [PATCH 07/32] Remove AddVeyMontAssignmentNodes --- .../vct/rewrite/veymont/SplitSeqGuards.scala | 2 +- .../vct/rewrite/veymont/StructureCheck.scala | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala b/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala index fdd7874ce1..2cc1fbd317 100644 --- a/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala +++ b/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala @@ -26,7 +26,7 @@ object SplitSeqGuards extends RewriterBuilder { case class SeqProgParticipantErrors(es: Seq[SeqProgParticipant]) extends UserError { override def code: String = "seqProgParticipantErrors" override def text: String = es.map { - case err: SeqProgParticipant => err.message { (n, m) => n.o.bareMessageInContext(m) } + case err: SeqProgParticipant => err.message { n => n.o } }.mkString("\n") } } diff --git a/src/rewrite/vct/rewrite/veymont/StructureCheck.scala b/src/rewrite/vct/rewrite/veymont/StructureCheck.scala index 8af70cef17..0930229885 100644 --- a/src/rewrite/vct/rewrite/veymont/StructureCheck.scala +++ b/src/rewrite/vct/rewrite/veymont/StructureCheck.scala @@ -2,7 +2,6 @@ package vct.col.rewrite.veymont import hre.util.ScopedStack import vct.col.ast._ -import vct.col.rewrite.veymont.AddVeyMontAssignmentNodes.{getDerefsFromExpr,getThreadDeref} import vct.col.rewrite.veymont.StructureCheck.VeyMontStructCheckError import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} import vct.result.VerificationError.UserError @@ -79,13 +78,12 @@ case class StructureCheck[Pre <: Generation]() extends Rewriter[Pre] { case ThisSeqProg(_) => if (args.isEmpty) rewriteDefault(st) else throw VeyMontStructCheckError(st, "Calls to methods in seq_program cannot have any arguments!") - case EndpointUse(thread) => - val argderefs = args.flatMap(getDerefsFromExpr) - val argthreads = argderefs.map(d => getThreadDeref(d, - VeyMontStructCheckError(st, "A method call on a thread object may only refer to a thread in its arguments!"))) - if (argthreads.forall(_ == thread.decl)) - rewriteDefault(st) - else throw VeyMontStructCheckError(st, "A method call on a thread object may only refer to same thread in its arguments!") + case EndpointUse(thread) => ??? + // val argderefs = ??? // args.flatMap(getDerefsFromExpr) + // val argthreads = argderefs.map(d => ???) // getThreadDeref(d, VeyMontStructCheckError(st, "A method call on a thread object may only refer to a thread in its arguments!"))) + // if (argthreads.forall(_ == thread.decl)) + // rewriteDefault(st) + // else throw VeyMontStructCheckError(st, "A method call on a thread object may only refer to same thread in its arguments!") case _ => throw VeyMontStructCheckError(st, "This kind of method call is not allowed in seq_program") } case _ => throw VeyMontStructCheckError(st, "This is not a method call") From 097a0e11ea49d1d1d95abeb02c62fdcec2bec532 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 15 Nov 2023 10:07:12 +0100 Subject: [PATCH 08/32] Last few broken code by deletion of AddVeyMontAssignNodes --- src/main/vct/main/stages/Transformation.scala | 4 ++-- .../vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index 2ce8839d25..fade2be1b4 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -14,7 +14,7 @@ import vct.col.rewrite.adt._ import vct.col.rewrite.bip._ import vct.col.rewrite.exc._ import vct.rewrite.lang.NoSupportSelfLoop -import vct.col.rewrite.veymont.{AddVeyMontAssignmentNodes, StructureCheck} +import vct.col.rewrite.veymont.StructureCheck import vct.importer.{PathAdtImporter, Util} import vct.main.Main.TemporarilyUnsupported import vct.main.stages.Transformation.TransformationCheckError @@ -311,7 +311,7 @@ case class SilverTransformation case class VeyMontTransformation(override val onBeforePassKey: Seq[(String, Verification[_ <: Generation] => Unit)] = Nil, override val onAfterPassKey: Seq[(String, Verification[_ <: Generation] => Unit)] = Nil) extends Transformation(onBeforePassKey, onAfterPassKey, Seq( - AddVeyMontAssignmentNodes, + // AddVeyMontAssignmentNodes, // AddVeyMontConditionNodes, StructureCheck, )) diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala b/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala index 41587f67c1..3b16f93739 100644 --- a/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala @@ -12,11 +12,6 @@ object EncodeSeqBranchUnanimity extends RewriterBuilder { override def key: String = "encodeSeqBranchUnanimity" override def desc: String = "Encodes the branch unanimity requirement imposed by VeyMont on branches and loops in seq_program nodes." - case class AddVeyMontConditionError(node : Node[_], msg: String) extends UserError { - override def code: String = "addVeyMontConditionError" - override def text: String = node.o.messageInContext(msg) - } - case class ForwardBranchUnanimity(branch: SeqBranch[_], c1: SeqGuard[_], c2: SeqGuard[_]) extends Blame[AssertFailed] { override def blame(error: AssertFailed): Unit = branch.blame.blame(BranchUnanimityFailed(c1, c2)) From 5cea5973acf6f020fa2f6fb415ff262a010c0a15 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 15 Nov 2023 12:13:19 +0100 Subject: [PATCH 09/32] Basic support for SeqLoop --- src/col/vct/col/ast/Node.scala | 5 ++ .../vct/col/ast/statement/StatementImpl.scala | 4 +- src/col/vct/col/origin/Blame.scala | 27 +++++++++ .../vct/col/typerules/CoercingRewriter.scala | 3 + .../vct/parsers/transform/PVLToCol.scala | 2 +- .../ResolveExpressionSideEffects.scala | 3 + .../vct/rewrite/lang/LangPVLToCol.scala | 7 +++ .../vct/rewrite/lang/LangSpecificToCol.scala | 1 + .../vct/rewrite/lang/LangVeyMontToCol.scala | 4 ++ .../vct/rewrite/veymont/SplitSeqGuards.scala | 2 +- .../examples/TechnicalVeyMontSpec.scala | 60 ++++++++++++++++++- 11 files changed, 114 insertions(+), 4 deletions(-) diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index 5b2aa367a7..3de41a741f 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -157,6 +157,7 @@ final case class IterVariable[G](variable: Variable[G], from: Expr[G], to: Expr[ sealed trait Statement[G] extends NodeFamily[G] with StatementImpl[G] final case class PVLBranch[G](branches: Seq[(Expr[G], Statement[G])])(val blame: Blame[FrontendIfFailure])(implicit val o: Origin) extends Statement[G] +final case class PVLLoop[G](init: Statement[G], cond: Expr[G], update: Statement[G], contract: LoopContract[G], body: Statement[G])(val blame: Blame[FrontEndLoopFailure])(implicit val o: Origin) extends Statement[G] sealed trait NonExecutableStatement[G] extends Statement[G] with NonExecutableStatementImpl[G] final case class LocalDecl[G](local: Variable[G])(implicit val o: Origin) extends NonExecutableStatement[G] with LocalDeclImpl[G] @@ -1284,10 +1285,14 @@ final case class SeqAssign[G](receiver: Ref[G, Endpoint[G]], field: Ref[G, Insta final case class EndpointUse[G](ref: Ref[G, Endpoint[G]])(implicit val o: Origin) extends Expr[G] with EndpointUseImpl[G] final case class UnresolvedSeqBranch[G](branches: Seq[(Expr[G], Statement[G])])(val blame: Blame[SeqBranchFailure])(implicit val o: Origin) extends Statement[G] +final case class UnresolvedSeqLoop[G](cond: Expr[G], contract: LoopContract[G], body: Statement[G])(val blame: Blame[SeqLoopFailure])(implicit val o: Origin) extends Statement[G] + sealed trait SeqGuard[G] extends NodeFamily[G] with SeqGuardImpl[G] final case class EndpointGuard[G](endpoint: Ref[G, Endpoint[G]], condition: Expr[G])(implicit val o: Origin) extends SeqGuard[G] with EndpointGuardImpl[G] final case class UnpointedGuard[G](condition: Expr[G])(implicit val o: Origin) extends SeqGuard[G] with UnpointedGuardImpl[G] + final case class SeqBranch[G](guards: Seq[SeqGuard[G]], yes: Statement[G], no: Option[Statement[G]])(val blame: Blame[SeqBranchFailure])(implicit val o: Origin) extends Statement[G] with SeqBranchImpl[G] +final case class SeqLoop[G](guards: Seq[SeqGuard[G]], contract: LoopContract[G], body: Statement[G])(val blame: Blame[SeqBranchFailure])(implicit val o: Origin) extends Statement[G] final case class VeyMontAssignExpression[G](endpoint: Ref[G, Endpoint[G]], assign: Statement[G])(implicit val o: Origin) extends Statement[G] with VeyMontAssignExpressionImpl[G] final case class CommunicateX[G](receiver: Ref[G, Endpoint[G]], sender: Ref[G, Endpoint[G]], chanType: Type[G], assign: Statement[G])(implicit val o: Origin) extends Statement[G] with CommunicateXImpl[G] diff --git a/src/col/vct/col/ast/statement/StatementImpl.scala b/src/col/vct/col/ast/statement/StatementImpl.scala index 653658feb6..cb616e299c 100644 --- a/src/col/vct/col/ast/statement/StatementImpl.scala +++ b/src/col/vct/col/ast/statement/StatementImpl.scala @@ -26,8 +26,10 @@ trait StatementImpl[G] extends NodeFamilyImpl[G] { this: Statement[G] => _: Block[G] | _: Eval[G] | _: Assert[G] | + _: UnresolvedSeqBranch[G] | + _: UnresolvedSeqLoop[G] | _: SeqBranch[G] | - _: UnresolvedSeqBranch[G] + _: SeqLoop[G] => Seq() case _ => Seq(SeqProgStatement(this)) } diff --git a/src/col/vct/col/origin/Blame.scala b/src/col/vct/col/origin/Blame.scala index d584973a72..8826f90b10 100644 --- a/src/col/vct/col/origin/Blame.scala +++ b/src/col/vct/col/origin/Blame.scala @@ -320,6 +320,33 @@ case class BranchUnanimityFailed(guard1: Node[_], guard2: Node[_]) extends SeqBr override def inlineDesc: String = "Two conditions in this branch might disagree." } +sealed trait FrontEndLoopFailure extends VerificationFailure +sealed trait SeqLoopFailure extends FrontEndLoopFailure + +case class LoopUnanimityNotEstablished(guard1: Node[_], guard2: Node[_]) extends SeqLoopFailure { + override def code: String = "loopUnanimityNotEstablished" + + override def desc: String = Message.messagesInContext( + (guard1.o, "This condition..."), + (guard2.o, "...should agree with this condition, but this could not be established before the loop.") + ) + + override def position: String = guard1.o.shortPositionText + override def inlineDesc: String = "The agreement of two conditions in this branch could not be established before the loop." +} + +case class LoopUnanimityMaintained(guard1: Node[_], guard2: Node[_]) extends SeqLoopFailure { + override def code: String = "loopUnanimityNotMaintained" + + override def desc: String = Message.messagesInContext( + (guard1.o, "This condition..."), + (guard2.o, "...should agree with this condition, but this could not be maintained for an arbitrary loop iteration.") + ) + + override def position: String = guard1.o.shortPositionText + override def inlineDesc: String = "The agreement of two conditions in this branch could not be maintained for an arbitrary loop iteration." +} + sealed trait DerefInsufficientPermission extends FrontendDerefError case class InsufficientPermission(node: HeapDeref[_]) extends DerefInsufficientPermission with NodeVerificationFailure { override def code: String = "perm" diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index 269424d46a..38e2c6b003 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -1700,8 +1700,11 @@ abstract class CoercingRewriter[Pre <: Generation]() extends AbstractRewriter[Pr throw err } case s: SeqBranch[Pre] => s + case s: SeqLoop[Pre] => s case branch@UnresolvedSeqBranch(branches) => UnresolvedSeqBranch(branches.map { case (cond, effect) => (bool(cond), effect) })(branch.blame) case branch@PVLBranch(branches) => PVLBranch(branches.map { case (cond, effect) => (bool(cond), effect) })(branch.blame) + case loop@UnresolvedSeqLoop(cond, contract, body) => UnresolvedSeqLoop(bool(cond), contract, body)(loop.blame) + case loop@PVLLoop(init, cond, update, contract, body) => PVLLoop(init, bool(cond), update, contract, body)(loop.blame) } } diff --git a/src/parsers/vct/parsers/transform/PVLToCol.scala b/src/parsers/vct/parsers/transform/PVLToCol.scala index aae2155886..e8ee23565a 100644 --- a/src/parsers/vct/parsers/transform/PVLToCol.scala +++ b/src/parsers/vct/parsers/transform/PVLToCol.scala @@ -335,7 +335,7 @@ case class PVLToCol[G](override val baseOrigin: Origin, ParAtomic(convert(invs).map(new UnresolvedRef[G, ParInvariantDecl[G]](_)), convert(body))(blame(stat)) case PvlWhile(contract, _, _, cond, _, body) => withContract(contract, contract => - Scope(Nil, Loop(Block(Nil), convert(cond), Block(Nil), contract.consumeLoopContract(stat), convert(body))) + Scope(Nil, PVLLoop(Block(Nil), convert(cond), Block(Nil), contract.consumeLoopContract(stat), convert(body))(blame(stat))) ) case PvlFor(contract, _, _, init, _, cond, _, update, _, body) => withContract(contract, contract => diff --git a/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala b/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala index d57e2ed50d..5519b00dde 100644 --- a/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala +++ b/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala @@ -345,8 +345,11 @@ case class ResolveExpressionSideEffects[Pre <: Generation]() extends Rewriter[Pr case comm: PVLCommunicate[Pre] => rewriteDefault(comm) case comm: Communicate[Pre] => rewriteDefault(comm) case _: PVLBranch[Pre] => throw ExtraNode + case _: PVLLoop[Pre] => throw ExtraNode case _: UnresolvedSeqBranch[Pre] => throw ExtraNode + case _: UnresolvedSeqLoop[Pre] => throw ExtraNode case _: SeqBranch[Pre] => throw ExtraNode + case _: SeqLoop[Pre] => throw ExtraNode case _: CStatement[Pre] => throw ExtraNode case _: CPPStatement[Pre] => throw ExtraNode case _: JavaStatement[Pre] => throw ExtraNode diff --git a/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala b/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala index 6af830c30e..807b921084 100644 --- a/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala @@ -138,4 +138,11 @@ case class LangPVLToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends L } else { Branch(branch.branches.map { case (e, s) => (rw.dispatch(e), rw.dispatch(s)) })(branch.o) } + + def loop(loop: PVLLoop[Pre]): Statement[Post] = loop match { + case PVLLoop(Block(Nil), _, Block(Nil), _, _) if rw.veymont.currentProg.nonEmpty => + rw.veymont.rewriteLoop(loop) + case PVLLoop(init, cond, update, contract, body) => + Loop(rw.dispatch(init), rw.dispatch(cond), rw.dispatch(update), rw.dispatch(contract), rw.dispatch(body))(loop.o) + } } diff --git a/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala b/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala index 0ad18db872..c386bc6755 100644 --- a/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala @@ -159,6 +159,7 @@ case class LangSpecificToCol[Pre <: Generation]() extends Rewriter[Pre] with Laz }._1) case branch: PVLBranch[Pre] => pvl.branch(branch) + case loop: PVLLoop[Pre] => pvl.loop(loop) case JavaLocalDeclarationStatement(locals: JavaLocalDeclaration[Pre]) => java.initLocal(locals) diff --git a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala index 72fd09a48a..c2766538a7 100644 --- a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala @@ -99,4 +99,8 @@ case class LangVeyMontToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) exten def rewriteBranch(branch: PVLBranch[Pre]): UnresolvedSeqBranch[Post] = UnresolvedSeqBranch(branch.branches.map { case (e, s) => (rw.dispatch(e), rw.dispatch(s)) })(branch.blame)(branch.o) + + def rewriteLoop(loop: PVLLoop[Pre]): UnresolvedSeqLoop[Post] = + UnresolvedSeqLoop(rw.dispatch(loop.cond), rw.dispatch(loop.contract), rw.dispatch(loop.body))(loop.blame)(loop.o) + } diff --git a/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala b/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala index 2cc1fbd317..7d5ba0c0f3 100644 --- a/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala +++ b/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala @@ -60,7 +60,7 @@ case class SplitSeqGuards[Pre <: Generation]() extends Rewriter[Pre] { assert(branch.branches.nonEmpty) unfoldBranch(branch.branches)(branch.blame, branch.o) - case l: Loop[Pre] if currentProg.nonEmpty => + case loop: UnresolvedSeqLoop[Pre] if currentProg.nonEmpty => ??? case statement => rewriteDefault(statement) diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index f660e63a76..c1fa53247d 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -352,4 +352,62 @@ class TechnicalVeyMontSpec extends VercorsSpec { } } """ -} \ No newline at end of file +} + +class TechnicalVeyMontSpec2 extends VercorsSpec { + vercors should verify using silicon in "Programs where branch conditions agree should verify" pvl + """ + class Storage { + bool x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + + seq_run { + alice.x := true; + bob.x := true; + while (alice.x && bob.x) { + bob.x := false; + communicate alice.x <- bob.x; + } + } + } + """ + + vercors should fail withCode "loopUnanimityNotEstablished" using silicon in "Programs where branch condition unanimity cannot be established should fail" pvl + """ + class Storage { + bool x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + + seq_run { + while (alice.x && bob.x) { + + } + } + } + """ + + vercors should fail withCode "loopUnanimityNotMaintained" using silicon in "Programs where branch condition unanimity cannot be maintained should fail" pvl + """ + class Storage { + bool x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + + seq_run { + alice.x := true; + bob.x := true; + while (alice.x && bob.x) { + alice.x := false; + } + } + } + """ +} From e45ae73e7b22c591fc4f6567ec379e979b935f23 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 15 Nov 2023 14:12:41 +0100 Subject: [PATCH 10/32] First draft branch unanimity for SeqLoop --- src/col/vct/col/ast/Node.scala | 2 +- .../ast/family/seqguard/SeqGuardImpl.scala | 2 +- .../ast/statement/veymont/SeqLoopImpl.scala | 18 +++++++ src/col/vct/col/origin/Blame.scala | 2 +- .../veymont/DeduplicateSeqGuards.scala | 7 +++ .../veymont/EncodeSeqBranchUnanimity.scala | 52 +++++++++++++++++-- .../veymont/EncodeUnpointedGuard.scala | 13 ++++- .../vct/rewrite/veymont/SplitSeqGuards.scala | 6 ++- .../examples/TechnicalVeyMontSpec.scala | 2 - 9 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 src/col/vct/col/ast/statement/veymont/SeqLoopImpl.scala diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index 3de41a741f..a375b62e3c 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -1292,7 +1292,7 @@ final case class EndpointGuard[G](endpoint: Ref[G, Endpoint[G]], condition: Expr final case class UnpointedGuard[G](condition: Expr[G])(implicit val o: Origin) extends SeqGuard[G] with UnpointedGuardImpl[G] final case class SeqBranch[G](guards: Seq[SeqGuard[G]], yes: Statement[G], no: Option[Statement[G]])(val blame: Blame[SeqBranchFailure])(implicit val o: Origin) extends Statement[G] with SeqBranchImpl[G] -final case class SeqLoop[G](guards: Seq[SeqGuard[G]], contract: LoopContract[G], body: Statement[G])(val blame: Blame[SeqBranchFailure])(implicit val o: Origin) extends Statement[G] +final case class SeqLoop[G](guards: Seq[SeqGuard[G]], contract: LoopContract[G], body: Statement[G])(val blame: Blame[SeqLoopFailure])(implicit val o: Origin) extends Statement[G] with SeqLoopImpl[G] final case class VeyMontAssignExpression[G](endpoint: Ref[G, Endpoint[G]], assign: Statement[G])(implicit val o: Origin) extends Statement[G] with VeyMontAssignExpressionImpl[G] final case class CommunicateX[G](receiver: Ref[G, Endpoint[G]], sender: Ref[G, Endpoint[G]], chanType: Type[G], assign: Statement[G])(implicit val o: Origin) extends Statement[G] with CommunicateXImpl[G] diff --git a/src/col/vct/col/ast/family/seqguard/SeqGuardImpl.scala b/src/col/vct/col/ast/family/seqguard/SeqGuardImpl.scala index ed2642d65a..0f7433cac0 100644 --- a/src/col/vct/col/ast/family/seqguard/SeqGuardImpl.scala +++ b/src/col/vct/col/ast/family/seqguard/SeqGuardImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.family.seqguard -import vct.col.ast.{Endpoint, Expr, SeqGuard} +import vct.col.ast.{Endpoint, Expr, SeqGuard, UnpointedGuard} trait SeqGuardImpl[G] { this: SeqGuard[G] => def condition: Expr[G] diff --git a/src/col/vct/col/ast/statement/veymont/SeqLoopImpl.scala b/src/col/vct/col/ast/statement/veymont/SeqLoopImpl.scala new file mode 100644 index 0000000000..186fe6c37b --- /dev/null +++ b/src/col/vct/col/ast/statement/veymont/SeqLoopImpl.scala @@ -0,0 +1,18 @@ +package vct.col.ast.statement.veymont + +import vct.col.ast.{Access, Communicate, Endpoint, EndpointGuard, EndpointName, SeqAssign, SeqBranch, SeqLoop, UnpointedGuard} +import vct.col.ref.Ref + +import scala.collection.immutable.ListSet + +trait SeqLoopImpl[G] { this: SeqLoop[G] => + def hasUnpointed: Boolean = guards.exists { case _: UnpointedGuard[G] => true; case _ => false } + def explicitParticipants: Seq[Endpoint[G]] = guards.collect { case EndpointGuard(Ref(endpoint), condition) => endpoint } + + def participants: Set[Endpoint[G]] = + ListSet.from(subnodes.collect { + case Communicate(Access(EndpointName(Ref(receiver)), _), Access(EndpointName(Ref(sender)), _)) => Seq(receiver, sender) + case SeqAssign(Ref(receiver), _, _) => Seq(receiver) + case branch: SeqBranch[G] => branch.explicitParticipants + }.flatten) +} diff --git a/src/col/vct/col/origin/Blame.scala b/src/col/vct/col/origin/Blame.scala index 8826f90b10..708f19d5c8 100644 --- a/src/col/vct/col/origin/Blame.scala +++ b/src/col/vct/col/origin/Blame.scala @@ -335,7 +335,7 @@ case class LoopUnanimityNotEstablished(guard1: Node[_], guard2: Node[_]) extends override def inlineDesc: String = "The agreement of two conditions in this branch could not be established before the loop." } -case class LoopUnanimityMaintained(guard1: Node[_], guard2: Node[_]) extends SeqLoopFailure { +case class LoopUnanimityNotMaintained(guard1: Node[_], guard2: Node[_]) extends SeqLoopFailure { override def code: String = "loopUnanimityNotMaintained" override def desc: String = Message.messagesInContext( diff --git a/src/rewrite/vct/rewrite/veymont/DeduplicateSeqGuards.scala b/src/rewrite/vct/rewrite/veymont/DeduplicateSeqGuards.scala index 96b79bf688..53dc628d73 100644 --- a/src/rewrite/vct/rewrite/veymont/DeduplicateSeqGuards.scala +++ b/src/rewrite/vct/rewrite/veymont/DeduplicateSeqGuards.scala @@ -26,6 +26,13 @@ case class DeduplicateSeqGuards[Pre <: Generation]() extends Rewriter[Pre] { } branch.rewrite(guards = dedup(guards)) + case loop: SeqLoop[Pre] => + val guards: Seq[EndpointGuard[Pre]] = loop.guards.map { + case guard: EndpointGuard[Pre] => guard + case guard: UnpointedGuard[Pre] => ??? // Excluded by RemoveUnpointedGuard + } + loop.rewrite(guards = dedup(guards)) + case _ => rewriteDefault(statement) } diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala b/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala index 3b16f93739..a926a72973 100644 --- a/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala @@ -1,12 +1,12 @@ package vct.rewrite.veymont import vct.col.ast._ -import vct.col.origin.{AssertFailed, Blame, BranchUnanimityFailed} +import vct.col.origin.{AssertFailed, Blame, BranchUnanimityFailed, LoopUnanimityNotEstablished, LoopUnanimityNotMaintained, Origin} import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} import vct.col.util.AstBuildHelpers._ import vct.col.util.SuccessionMap import vct.result.VerificationError.UserError -import vct.rewrite.veymont.EncodeSeqBranchUnanimity.ForwardBranchUnanimity +import vct.rewrite.veymont.EncodeSeqBranchUnanimity.{ForwardBranchUnanimity, ForwardLoopUnanimityNotEstablished, ForwardLoopUnanimityNotMaintained} object EncodeSeqBranchUnanimity extends RewriterBuilder { override def key: String = "encodeSeqBranchUnanimity" @@ -16,6 +16,16 @@ object EncodeSeqBranchUnanimity extends RewriterBuilder { override def blame(error: AssertFailed): Unit = branch.blame.blame(BranchUnanimityFailed(c1, c2)) } + + case class ForwardLoopUnanimityNotEstablished(loop: SeqLoop[_], c1: SeqGuard[_], c2: SeqGuard[_]) extends Blame[AssertFailed] { + override def blame(error: AssertFailed): Unit = + loop.blame.blame(LoopUnanimityNotEstablished(c1, c2)) + } + + case class ForwardLoopUnanimityNotMaintained(loop: SeqLoop[_], c1: SeqGuard[_], c2: SeqGuard[_]) extends Blame[AssertFailed] { + override def blame(error: AssertFailed): Unit = + loop.blame.blame(LoopUnanimityNotMaintained(c1, c2)) + } } case class EncodeSeqBranchUnanimity[Pre <: Generation]() extends Rewriter[Pre] { @@ -37,7 +47,7 @@ case class EncodeSeqBranchUnanimity[Pre <: Generation]() extends Rewriter[Pre] { if (cons) dispatch(yes) dispatch(no) */ val assignments: Seq[Assign[Post]] = guards.map { guard => - guardSucc(guard) = new Variable(TBool()) + guardSucc(guard) = new Variable(TBool())(guard.o.where(name = "g")) assignLocal(guardSucc(guard).get, dispatch(guard.condition)) } @@ -65,6 +75,42 @@ case class EncodeSeqBranchUnanimity[Pre <: Generation]() extends Rewriter[Pre] { finalIf )) + case loop@SeqLoop(guards, contract, body) => + implicit val o = statement.o + + val assignments: Seq[Assign[Post]] = guards.map { guard => + guardSucc(guard) = new Variable(TBool())(guard.o.where(name = "g")) + assignLocal(guardSucc(guard).get, dispatch(guard.condition)) + } + + val establishAssertions: Seq[Assert[Post]] = (0 until guards.length - 1).map { i => + val c1 = guards(i) + val c2 = guards(i + 1) + Assert(guardSucc(c1).get === guardSucc(c2).get)(ForwardLoopUnanimityNotEstablished(loop, c1, c2)) + } + + val maintainAssertions: Seq[Assert[Post]] = (0 until guards.length - 1).map { i => + val c1 = guards(i) + val c2 = guards(i + 1) + Assert(guardSucc(c1).get === guardSucc(c2).get)(ForwardLoopUnanimityNotMaintained(loop, c1, c2)) + } + + val combinedCond = new Variable[Post](TBool())(loop.o.where(name = "combined")) + val combinedAssign: Assign[Post] = assignLocal[Post]( + combinedCond.get, + foldAnd(guards.map(guard => guardSucc(guard).get)) + ) + + val finalLoop: Loop[Post] = Loop( + Block(assignments ++ establishAssertions :+ combinedAssign), + combinedCond.get, + Block(assignments ++ maintainAssertions :+ combinedAssign), + dispatch(contract), + dispatch(body) + ) + + Scope(guards.map(guardSucc(_)) :+ combinedCond, finalLoop) + case statement => rewriteDefault(statement) } } diff --git a/src/rewrite/vct/rewrite/veymont/EncodeUnpointedGuard.scala b/src/rewrite/vct/rewrite/veymont/EncodeUnpointedGuard.scala index d3ed153f16..380f14206c 100644 --- a/src/rewrite/vct/rewrite/veymont/EncodeUnpointedGuard.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeUnpointedGuard.scala @@ -14,7 +14,7 @@ import scala.collection.immutable.ListSet object EncodeUnpointedGuard extends RewriterBuilder { override def key: String = "encodeUnpointedGuard" - override def desc: String = "Removes unpointed guard by duplicating the condition to all guards currently participating" + override def desc: String = "Encodes unpointed guard by duplicating the condition to all guards currently participating" } case class EncodeUnpointedGuard[Pre <: Generation]() extends Rewriter[Pre] { @@ -38,6 +38,17 @@ case class EncodeUnpointedGuard[Pre <: Generation]() extends Rewriter[Pre] { currentParticipants.having(newParticipants) { branch.rewrite(guards = branch.guards.flatMap(rewriteGuard)) } + + case loop: SeqLoop[Pre] => + val newParticipants = if (loop.hasUnpointed) { + currentParticipants.top + } else { + ListSet.from(loop.participants) + } + currentParticipants.having(newParticipants) { + loop.rewrite(guards = loop.guards.flatMap(rewriteGuard)) + } + case statement => rewriteDefault(statement) } diff --git a/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala b/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala index 7d5ba0c0f3..8e2909ee6b 100644 --- a/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala +++ b/src/rewrite/vct/rewrite/veymont/SplitSeqGuards.scala @@ -61,7 +61,11 @@ case class SplitSeqGuards[Pre <: Generation]() extends Rewriter[Pre] { unfoldBranch(branch.branches)(branch.blame, branch.o) case loop: UnresolvedSeqLoop[Pre] if currentProg.nonEmpty => - ??? + SeqLoop( + inferSeqGuard(loop.cond), + dispatch(loop.contract), + dispatch(loop.body) + )(loop.blame)(loop.o) case statement => rewriteDefault(statement) } diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index c1fa53247d..d99db5332e 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -352,9 +352,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { } } """ -} -class TechnicalVeyMontSpec2 extends VercorsSpec { vercors should verify using silicon in "Programs where branch conditions agree should verify" pvl """ class Storage { From 8a9e5e1312430e7850bc42782bb9d012f9e04962 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 15 Nov 2023 14:45:00 +0100 Subject: [PATCH 11/32] Add some tests for the interaction between loops, branches and participants --- .../ast/statement/veymont/SeqLoopImpl.scala | 30 +++++++- .../examples/TechnicalVeyMontSpec.scala | 69 +++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/col/vct/col/ast/statement/veymont/SeqLoopImpl.scala b/src/col/vct/col/ast/statement/veymont/SeqLoopImpl.scala index 186fe6c37b..7c5d27e5be 100644 --- a/src/col/vct/col/ast/statement/veymont/SeqLoopImpl.scala +++ b/src/col/vct/col/ast/statement/veymont/SeqLoopImpl.scala @@ -1,14 +1,42 @@ package vct.col.ast.statement.veymont +import vct.col.ast.statement.StatementImpl import vct.col.ast.{Access, Communicate, Endpoint, EndpointGuard, EndpointName, SeqAssign, SeqBranch, SeqLoop, UnpointedGuard} +import vct.col.check.{CheckContext, CheckError, SeqProgParticipant} import vct.col.ref.Ref import scala.collection.immutable.ListSet -trait SeqLoopImpl[G] { this: SeqLoop[G] => +trait SeqLoopImpl[G] extends StatementImpl[G] { this: SeqLoop[G] => def hasUnpointed: Boolean = guards.exists { case _: UnpointedGuard[G] => true; case _ => false } def explicitParticipants: Seq[Endpoint[G]] = guards.collect { case EndpointGuard(Ref(endpoint), condition) => endpoint } + override def enterCheckContext(context: CheckContext[G]): CheckContext[G] = { + // Assume SeqProg sets participatingEndpoints + assert(context.currentParticipatingEndpoints.isDefined) + + if (hasUnpointed) { + // Everyone that is participating keeps participating, as well as any endpoints explicitly mentioned + context.appendCurrentParticipatingEndpoints(explicitParticipants) + } else { + // We can refine the set of participants down to the set of endpoints actually present in the guard + context.withCurrentParticipatingEndpoints(explicitParticipants) + } + } + + override def check(context: CheckContext[G]): Seq[CheckError] = super.check(context) ++ { + // Assume SeqProg sets participatingEndpoints + assert(context.currentParticipatingEndpoints.isDefined) + + // Ensure the set of participants is at most refined + if (Set.from(explicitParticipants).subsetOf(context.currentParticipatingEndpoints.get)) { + Seq() + } else { + // There are participants in this if that have been excluded from participation: error + Seq(SeqProgParticipant(this)) + } + } + def participants: Set[Endpoint[G]] = ListSet.from(subnodes.collect { case Communicate(Access(EndpointName(Ref(receiver)), _), Access(EndpointName(Ref(sender)), _)) => Seq(receiver, sender) diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index d99db5332e..2e042d9fbc 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -408,4 +408,73 @@ class TechnicalVeyMontSpec extends VercorsSpec { } } """ + + vercors should error withCode "seqProgParticipantErrors" in "Loops should also limit the number of participants" pvl + """ + class Storage { + bool x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + endpoint charlie = Storage(); + + seq_run { + alice.x := true; + bob.x := true; + while (alice.x && bob.x) { + alice.x := false; + charlie.x := true; + } + } + } + """ + + vercors should verify using silicon in "Loops should also limit the number of participants when combined with branches" pvl + """ + class Storage { + bool x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + endpoint charlie = Storage(); + + seq_run { + alice.x := true; + bob.x := true; + while (alice.x && bob.x) { + alice.x := false; + if (bob.x == true) { + bob.x := false; + } + } + } + } + """ + + vercors should error withCode "seqProgParticipantErrors" in "Loops should also limit the number of participants when combined with branches" pvl + """ + class Storage { + bool x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + endpoint charlie = Storage(); + + seq_run { + alice.x := true; + bob.x := true; + while (alice.x && bob.x) { + alice.x := false; + if (bob.x == true) { + bob.x := false; + charlie.x := true; + } + } + } + } + """ + } From 905e18bb7d72a86e1b42c89d6fa8e868df5058da Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Thu, 16 Nov 2023 15:05:44 +0100 Subject: [PATCH 12/32] Add flag capability to test suite. Add veymont permission generation flag to options. --- src/main/vct/main/stages/Transformation.scala | 4 ++- src/main/vct/options/Options.scala | 5 +++ .../veymont/GenerateSeqProgPermissions.scala | 14 ++++---- .../examples/TechnicalVeyMontSpec.scala | 28 +++++++++++++++ .../test/integration/helper/VercorsSpec.scala | 35 ++++++++++++------- 5 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index fade2be1b4..bf362a62b7 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -67,6 +67,7 @@ object Transformation { inferHeapContextIntoFrame = options.inferHeapContextIntoFrame, bipResults = bipResults, splitVerificationByProcedure = options.devSplitVerificationByProcedure, + veymontGeneratePermissions = options.veymontGeneratePermissions, ) } @@ -172,6 +173,7 @@ case class SilverTransformation bipResults: BIP.VerificationResults, checkSat: Boolean = true, splitVerificationByProcedure: Boolean = false, + veymontGeneratePermissions: Boolean = false, ) extends Transformation(onBeforePassKey, onAfterPassKey, Seq( // Replace leftover SYCL types ReplaceSYCLTypes, @@ -193,7 +195,7 @@ case class SilverTransformation EncodeRangedFor, // VeyMont sequential program encoding - GenerateSeqProgPermissions, + GenerateSeqProgPermissions.withArg(veymontGeneratePermissions), SplitSeqGuards, EncodeUnpointedGuard, DeduplicateSeqGuards, diff --git a/src/main/vct/options/Options.scala b/src/main/vct/options/Options.scala index 412f696814..9a6112db75 100644 --- a/src/main/vct/options/Options.scala +++ b/src/main/vct/options/Options.scala @@ -240,6 +240,10 @@ case object Options { .action((path, c) => c.copy(cPreprocessorPath = path)) .text("Set the location of the C preprocessor binary"), + opt[Unit]("veymont-generate-permissions") + .action((_, c) => c.copy(veymontGeneratePermissions = true)) + .text("Generate permissions for the entire sequential program in the style of VeyMont 1.4"), + note(""), note("VeyMont Mode"), opt[Unit]("veymont") @@ -389,6 +393,7 @@ case class Options // VeyMont options veymontOutput: Path = null, // required veymontChannel: PathOrStd = PathOrStd.Path(getVeymontChannel), + veymontGeneratePermissions: Boolean = false, // VeSUV options vesuvOutput: Path = null, diff --git a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala index 141124feda..c2f1ecdcd4 100644 --- a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala +++ b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala @@ -7,14 +7,14 @@ import vct.col.util.AstBuildHelpers._ import vct.col.ast.{ApplicableContract, ArraySubscript, Declaration, Deref, Endpoint, EndpointUse, EnumUse, Expr, FieldLocation, InstanceField, IterationContract, Length, Local, LoopContract, LoopInvariant, Null, Perm, SeqProg, SeqRun, SplitAccountedPredicate, TArray, TClass, TInt, Type, UnitAccountedPredicate, WritePerm} import vct.col.origin.{Origin, PanicBlame} import vct.col.ref.Ref -import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} +import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder, RewriterBuilderArg} -object GenerateSeqProgPermissions extends RewriterBuilder { +object GenerateSeqProgPermissions extends RewriterBuilderArg[Boolean] { override def key: String = "generateSeqProgPermissions" - override def desc: String = "Generates permissions for fields of some types (int, bool, and arrays of simple datatypes) for constructs used inside seq_program." + override def desc: String = "Generates permissions for fields of some types (classes, int, bool, and arrays of these) for constructs used inside seq_program." } -case class GenerateSeqProgPermissions[Pre <: Generation]() extends Rewriter[Pre] with LazyLogging { +case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = false) extends Rewriter[Pre] with LazyLogging { val currentProg: ScopedStack[SeqProg[Pre]] = ScopedStack() val currentRun: ScopedStack[SeqRun[Pre]] = ScopedStack() @@ -28,7 +28,7 @@ case class GenerateSeqProgPermissions[Pre <: Generation]() extends Rewriter[Pre] override def dispatch(contract: ApplicableContract[Pre]): ApplicableContract[Post] = (currentProg.topOption, currentRun.topOption) match { - case (Some(prog), Some(_)) => + case (Some(prog), Some(_)) if enabled => implicit val o = contract.o contract.rewrite( requires = @@ -46,12 +46,12 @@ case class GenerateSeqProgPermissions[Pre <: Generation]() extends Rewriter[Pre] } override def dispatch(loopContract: LoopContract[Pre]): LoopContract[Post] = (currentProg.topOption, currentRun.topOption, loopContract) match { - case (Some(prog), Some(_), invariant: LoopInvariant[pre]) => + case (Some(prog), Some(_), invariant: LoopInvariant[pre]) if enabled => implicit val o = loopContract.o invariant.rewrite( invariant = foldStar[Post](prog.endpoints.map(endpointPerm) :+ dispatch(invariant.invariant)) ) - case (Some(prog), Some(_), iteration: IterationContract[pre]) => + case (Some(prog), Some(_), iteration: IterationContract[pre]) if enabled => implicit val o = loopContract.o iteration.rewrite( requires = foldStar[Post](prog.endpoints.map(endpointPerm) :+ dispatch(iteration.requires)), diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index 2e042d9fbc..872aa9f987 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -1,5 +1,9 @@ package vct.test.integration.examples +import hre.io.LiteralReadable +import vct.main.modes.Verify +import vct.options.Options +import vct.options.types.Verbosity import vct.test.integration.helper.VercorsSpec class TechnicalVeyMontSpec extends VercorsSpec { @@ -478,3 +482,27 @@ class TechnicalVeyMontSpec extends VercorsSpec { """ } + +class TechnicalVeyMontSpec2 extends VercorsSpec { + (vercors should verify + using silicon + flags Seq("--veymont-generate-permissions") + in "Loops should also limit the number of participants when combined with branches" pvl + """ + class Storage { + int x; + + int m() { + x = 2; + } + } + + seq_program Example() { + endpoint alice = Storage(); + seq_run { + alice.m(); + assert alice.x == 2; + } + } + """) +} \ No newline at end of file diff --git a/test/main/vct/test/integration/helper/VercorsSpec.scala b/test/main/vct/test/integration/helper/VercorsSpec.scala index 49fc15d24d..e5cc270a87 100644 --- a/test/main/vct/test/integration/helper/VercorsSpec.scala +++ b/test/main/vct/test/integration/helper/VercorsSpec.scala @@ -8,11 +8,12 @@ import org.scalatest.concurrent.TimeLimits.failAfter import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.time._ import org.slf4j.LoggerFactory +import scopt.OParser import vct.col.origin.{BlameUnreachable, VerificationFailure} import vct.col.rewrite.bip.BIP.Standalone.VerificationReport import vct.main.Main.TemporarilyUnsupported import vct.main.modes.Verify -import vct.options.types +import vct.options.{Options, types} import vct.options.types.{Backend, PathOrStd} import vct.parsers.ParseError import vct.result.VerificationError @@ -71,7 +72,7 @@ abstract class VercorsSpec extends AnyFlatSpec { } } - private def registerTest(verdict: Verdict, desc: String, tags: Seq[Tag], backend: Backend, inputs: Seq[Readable])(implicit pos: source.Position): Unit = { + private def registerTest(verdict: Verdict, desc: String, tags: Seq[Tag], backend: Backend, inputs: Seq[Readable], flags: Seq[String])(implicit pos: source.Position): Unit = { val fullDesc: String = s"${desc.capitalize} produces verdict $verdict with $backend".replaceAll("should", "shld") // PB: note that object typically do not have a deterministic hashCode, but Strings do. val matrixId = Math.floorMod(fullDesc.hashCode, MATRIX_COUNT) @@ -83,8 +84,16 @@ abstract class VercorsSpec extends AnyFlatSpec { failAfter(Span(300, Seconds)) { matchVerdict(verdict, backend match { - case types.Backend.Silicon => Verify.verifyWithSilicon(inputs) + case types.Backend.Silicon => + Verify.verifyWithOptions( + Options.parse((Seq("--backend", "silicon") ++ flags).toArray).get, + inputs + ) case types.Backend.Carbon => Verify.verifyWithCarbon(inputs) + Verify.verifyWithOptions( + Options.parse((Seq("--backend", "carbon") ++ flags).toArray).get, + inputs + ) }) } } @@ -167,14 +176,14 @@ abstract class VercorsSpec extends AnyFlatSpec { } class ErrorVerdictPhrase() { - def withCode(code: String): BackendPhrase = new BackendPhrase(Error(code), None, silicon) + def withCode(code: String): BackendPhrase = new BackendPhrase(Error(code), None, silicon, Nil) } class VerdictPhrase(val verdict: Verdict, val reportPath: Option[Path]) { - def using(backend: Seq[Backend]): BackendPhrase = new BackendPhrase(verdict, reportPath, backend) + def using(backend: Seq[Backend]): BackendPhrase = new BackendPhrase(verdict, reportPath, backend, Nil) } - class BackendPhrase(val verdict: Verdict, val reportPath: Option[Path], val backends: Seq[Backend]) { + class BackendPhrase(val verdict: Verdict, val reportPath: Option[Path], val backends: Seq[Backend], val _flags: Seq[String]) { def example(path: String)(implicit pos: source.Position): Unit = examples(path) def examples(examples: String*)(implicit pos: source.Position): Unit = { @@ -183,32 +192,34 @@ abstract class VercorsSpec extends AnyFlatSpec { val inputs = paths.map(PathOrStd.Path) for(backend <- backends) { - registerTest(verdict, s"Examples ${paths.mkString(", ")}", Seq(new Tag("exampleCase")), backend, inputs) + registerTest(verdict, s"Examples ${paths.mkString(", ")}", Seq(new Tag("exampleCase")), backend, inputs, _flags) } } - def in(desc: String): DescPhrase = new DescPhrase(verdict, backends, desc) + def in(desc: String): DescPhrase = new DescPhrase(verdict, backends, desc, _flags) + + def flags(args: Seq[String]): BackendPhrase = new BackendPhrase(verdict, reportPath, backends, args) } - class DescPhrase(val verdict: Verdict, val backends: Seq[Backend], val desc: String) { + class DescPhrase(val verdict: Verdict, val backends: Seq[Backend], val desc: String, val flags: Seq[String]) { def pvl(data: String)(implicit pos: source.Position): Unit = { val inputs = Seq(LiteralReadable("test.pvl", data)) for(backend <- backends) { - registerTest(verdict, desc, Seq(new Tag("literalCase")), backend, inputs) + registerTest(verdict, desc, Seq(new Tag("literalCase")), backend, inputs, flags) } } def java(data: String)(implicit pos: source.Position): Unit = { val inputs = Seq(LiteralReadable("test.java", data)) for(backend <- backends) { - registerTest(verdict, desc, Seq(new Tag("literalCase")), backend, inputs) + registerTest(verdict, desc, Seq(new Tag("literalCase")), backend, inputs, flags) } } def c(data: String)(implicit pos: source.Position): Unit = { val inputs = Seq(LiteralReadable("test.c", data)) for(backend <- backends) { - registerTest(verdict, desc, Seq(new Tag("literalCase")), backend, inputs) + registerTest(verdict, desc, Seq(new Tag("literalCase")), backend, inputs, flags) } } } From c0f3a0faa03a3dc0158958a5b9e69872af59342a Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Thu, 16 Nov 2023 17:14:01 +0100 Subject: [PATCH 13/32] First draft of enabling permissions program-wide --- .../veymont/GenerateSeqProgPermissions.scala | 68 ++++++++++--------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala index c2f1ecdcd4..5fff352f72 100644 --- a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala +++ b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala @@ -4,7 +4,7 @@ import com.typesafe.scalalogging.LazyLogging import hre.util.ScopedStack import vct.col.ast.RewriteHelpers._ import vct.col.util.AstBuildHelpers._ -import vct.col.ast.{ApplicableContract, ArraySubscript, Declaration, Deref, Endpoint, EndpointUse, EnumUse, Expr, FieldLocation, InstanceField, IterationContract, Length, Local, LoopContract, LoopInvariant, Null, Perm, SeqProg, SeqRun, SplitAccountedPredicate, TArray, TClass, TInt, Type, UnitAccountedPredicate, WritePerm} +import vct.col.ast.{ApplicableContract, ArraySubscript, Class, Declaration, Deref, Endpoint, EndpointUse, EnumUse, Expr, FieldLocation, InstanceField, IterationContract, Length, Local, LoopContract, LoopInvariant, Null, Perm, SeqProg, SeqRun, SplitAccountedPredicate, TArray, TClass, TInt, ThisObject, Type, UnitAccountedPredicate, WritePerm} import vct.col.origin.{Origin, PanicBlame} import vct.col.ref.Ref import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder, RewriterBuilderArg} @@ -16,50 +16,56 @@ object GenerateSeqProgPermissions extends RewriterBuilderArg[Boolean] { case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = false) extends Rewriter[Pre] with LazyLogging { - val currentProg: ScopedStack[SeqProg[Pre]] = ScopedStack() - val currentRun: ScopedStack[SeqRun[Pre]] = ScopedStack() + sealed trait Context + case class ClassContext(cls: Class[Pre]) extends Context + case class SeqContext(prog: SeqProg[Pre]) extends Context + + val currentContext: ScopedStack[Context] = ScopedStack() override def dispatch(decl: Declaration[Pre]): Unit = decl match { - case prog: SeqProg[Pre] => currentProg.having(prog) { rewriteDefault(prog) } + case prog: SeqProg[Pre] if enabled => currentContext.having(SeqContext(prog)) { rewriteDefault(prog) } + case cls: Class[Pre] if enabled => currentContext.having(ClassContext(cls)) { rewriteDefault(cls) } case decl => rewriteDefault(decl) } - override def dispatch(run: SeqRun[Pre]): SeqRun[Post] = currentRun.having(run) { rewriteDefault(run) } - - override def dispatch(contract: ApplicableContract[Pre]): ApplicableContract[Post] = - (currentProg.topOption, currentRun.topOption) match { - case (Some(prog), Some(_)) if enabled => - implicit val o = contract.o - contract.rewrite( - requires = - SplitAccountedPredicate[Post]( - UnitAccountedPredicate(foldStar[Post](prog.endpoints.map(endpointPerm))), - dispatch(contract.requires) - ), - ensures = - SplitAccountedPredicate[Post]( - UnitAccountedPredicate(foldStar[Post](prog.endpoints.map(endpointPerm))), - dispatch(contract.ensures) - ) - ) - case _ => rewriteDefault(contract) - } - - override def dispatch(loopContract: LoopContract[Pre]): LoopContract[Post] = (currentProg.topOption, currentRun.topOption, loopContract) match { - case (Some(prog), Some(_), invariant: LoopInvariant[pre]) if enabled => + override def dispatch(contract: ApplicableContract[Pre]): ApplicableContract[Post] = currentContext.topOption match { + case Some(ctx) => + implicit val o = contract.o + contract.rewrite( + requires = + SplitAccountedPredicate[Post]( + UnitAccountedPredicate(permissions(ctx)), + dispatch(contract.requires) + ), + ensures = + SplitAccountedPredicate[Post]( + UnitAccountedPredicate(permissions(ctx)), + dispatch(contract.ensures) + ) + ) + case _ => rewriteDefault(contract) + } + + override def dispatch(loopContract: LoopContract[Pre]): LoopContract[Post] = (currentContext.topOption, loopContract) match { + case (Some(ctx), invariant: LoopInvariant[pre]) => implicit val o = loopContract.o invariant.rewrite( - invariant = foldStar[Post](prog.endpoints.map(endpointPerm) :+ dispatch(invariant.invariant)) + invariant = foldStar[Post](Seq(permissions(ctx), dispatch(invariant.invariant))) ) - case (Some(prog), Some(_), iteration: IterationContract[pre]) if enabled => + case (Some(ctx), iteration: IterationContract[pre]) => implicit val o = loopContract.o iteration.rewrite( - requires = foldStar[Post](prog.endpoints.map(endpointPerm) :+ dispatch(iteration.requires)), - ensures = foldStar[Post](prog.endpoints.map(endpointPerm) :+ dispatch(iteration.ensures)) + requires = foldStar[Post](Seq(permissions(ctx), dispatch(iteration.requires))), + ensures = foldStar[Post](Seq(permissions(ctx), dispatch(iteration.ensures))) ) case _ => rewriteDefault(loopContract) } + def permissions(ctx: Context)(implicit o: Origin): Expr[Post] = ctx match { + case SeqContext(prog) => foldStar[Post](prog.endpoints.map(endpointPerm)) + case ClassContext(cls) => transitivePerm(ThisObject[Post](succ(cls)), TClass(cls.ref)) + } + def endpointPerm(endpoint: Endpoint[Pre])(implicit o: Origin): Expr[Post] = transitivePerm(EndpointUse[Post](succ(endpoint)), TClass(endpoint.cls)) From 3b3236a14e3c472cffb9bc083b636f0e2c26fbf0 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Fri, 17 Nov 2023 11:01:51 +0100 Subject: [PATCH 14/32] Make a start on generalizign perm generation a bit --- .../veymont/GenerateSeqProgPermissions.scala | 120 +++++++++++------- 1 file changed, 76 insertions(+), 44 deletions(-) diff --git a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala index 5fff352f72..83accbeea4 100644 --- a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala +++ b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala @@ -4,7 +4,7 @@ import com.typesafe.scalalogging.LazyLogging import hre.util.ScopedStack import vct.col.ast.RewriteHelpers._ import vct.col.util.AstBuildHelpers._ -import vct.col.ast.{ApplicableContract, ArraySubscript, Class, Declaration, Deref, Endpoint, EndpointUse, EnumUse, Expr, FieldLocation, InstanceField, IterationContract, Length, Local, LoopContract, LoopInvariant, Null, Perm, SeqProg, SeqRun, SplitAccountedPredicate, TArray, TClass, TInt, ThisObject, Type, UnitAccountedPredicate, WritePerm} +import vct.col.ast.{Applicable, ApplicableContract, ArraySubscript, BooleanValue, Class, ContractApplicable, Declaration, Deref, Endpoint, EndpointUse, EnumUse, Expr, FieldLocation, Function, InstanceField, InstanceFunction, IterationContract, Length, Local, LoopContract, LoopInvariant, Null, Perm, Procedure, Result, SeqProg, SeqRun, SplitAccountedPredicate, TArray, TClass, TInt, ThisObject, Type, UnitAccountedPredicate, Variable, WritePerm} import vct.col.origin.{Origin, PanicBlame} import vct.col.ref.Ref import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder, RewriterBuilderArg} @@ -16,59 +16,91 @@ object GenerateSeqProgPermissions extends RewriterBuilderArg[Boolean] { case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = false) extends Rewriter[Pre] with LazyLogging { - sealed trait Context - case class ClassContext(cls: Class[Pre]) extends Context - case class SeqContext(prog: SeqProg[Pre]) extends Context +// sealed trait Context +// case class ClassContext(cls: Class[Pre]) extends Context +// case class SeqContext(prog: SeqProg[Pre]) extends Context - val currentContext: ScopedStack[Context] = ScopedStack() + val currentPerm: ScopedStack[Expr[Post]] = ScopedStack() - override def dispatch(decl: Declaration[Pre]): Unit = decl match { - case prog: SeqProg[Pre] if enabled => currentContext.having(SeqContext(prog)) { rewriteDefault(prog) } - case cls: Class[Pre] if enabled => currentContext.having(ClassContext(cls)) { rewriteDefault(cls) } - case decl => rewriteDefault(decl) - } + /* - Permission generation table - + Only considered nodes so far that occur in early VeyMont case studies. - override def dispatch(contract: ApplicableContract[Pre]): ApplicableContract[Post] = currentContext.topOption match { - case Some(ctx) => - implicit val o = contract.o - contract.rewrite( - requires = - SplitAccountedPredicate[Post]( - UnitAccountedPredicate(permissions(ctx)), - dispatch(contract.requires) - ), - ensures = - SplitAccountedPredicate[Post]( - UnitAccountedPredicate(permissions(ctx)), - dispatch(contract.ensures) - ) - ) - case _ => rewriteDefault(contract) - } + Pre Post Invariant + Function: args N.a + Procedure: args args, return args - override def dispatch(loopContract: LoopContract[Pre]): LoopContract[Post] = (currentContext.topOption, loopContract) match { - case (Some(ctx), invariant: LoopInvariant[pre]) => - implicit val o = loopContract.o - invariant.rewrite( - invariant = foldStar[Post](Seq(permissions(ctx), dispatch(invariant.invariant))) - ) - case (Some(ctx), iteration: IterationContract[pre]) => - implicit val o = loopContract.o - iteration.rewrite( - requires = foldStar[Post](Seq(permissions(ctx), dispatch(iteration.requires))), - ensures = foldStar[Post](Seq(permissions(ctx), dispatch(iteration.ensures))) - ) - case _ => rewriteDefault(loopContract) - } + Instance function: args, fields N.a + Instance method: args, fields args, fields, return args, fields - def permissions(ctx: Context)(implicit o: Origin): Expr[Post] = ctx match { - case SeqContext(prog) => foldStar[Post](prog.endpoints.map(endpointPerm)) - case ClassContext(cls) => transitivePerm(ThisObject[Post](succ(cls)), TClass(cls.ref)) + SeqProg: args args N.a + SeqRun: endpoints endpoints endpoints + */ + + override def dispatch(decl: Declaration[Pre]): Unit = decl match { +// case prog: SeqProg[Pre] if enabled => currentContext.having(SeqContext(prog)) { rewriteDefault(prog) } + case fun: Function[Pre] if enabled => + globalDeclarations.declare(fun.rewrite( + contract = prependContract(fun.contract, variablesPerm(fun.args)(fun.o), tt)(fun.o) + )) + case proc: Procedure[Pre] if enabled => + implicit val o = proc.o + globalDeclarations.declare(proc.rewrite( + contract = prependContract( + proc.contract, + variablesPerm(proc.args), + variablesPerm(proc.args) &* resultPerm(proc)(proc.o)), + body = proc.body.map(body => currentPerm.having(variablesPerm(proc.args)) { dispatch(body) }) + )) + + case cls: Class[Pre] if enabled => currentPerm.having(???) { rewriteDefault(???) } + + case fun: InstanceFunction[Pre] if enabled => + + case decl => rewriteDefault(decl) } + def prependContract(contract: ApplicableContract[Pre], pre: Expr[Post], post: Expr[Post])(implicit o: Origin) = + contract.rewrite( + requires = pre match { + case BooleanValue(true) => dispatch(contract.requires) + case pre => SplitAccountedPredicate[Post](UnitAccountedPredicate(pre), dispatch(contract.requires)) + }, + ensures = post match { + case BooleanValue(true) => dispatch(contract.ensures) + case post => SplitAccountedPredicate[Post](UnitAccountedPredicate(post), dispatch(contract.ensures)) + } + ) + + override def dispatch(loopContract: LoopContract[Pre]): LoopContract[Post] = + (currentPerm.topOption, loopContract) match { + case (Some(perm), invariant: LoopInvariant[pre]) => + implicit val o = loopContract.o + invariant.rewrite(invariant = perm &* dispatch(invariant.invariant)) + case (Some(perm), iteration: IterationContract[pre]) => + implicit val o = loopContract.o + iteration.rewrite( + requires = perm &* dispatch(iteration.requires), + ensures = perm &* dispatch(iteration.ensures)) + case _ => rewriteDefault(loopContract) + } + +// def permissions(ctx: Context)(implicit o: Origin): Expr[Post] = ctx match { +// case SeqContext(prog) => foldStar[Post](prog.endpoints.map(endpointPerm)) +// case ClassContext(cls) => transitivePerm(ThisObject[Post](succ(cls)), TClass(cls.ref)) +// } + def endpointPerm(endpoint: Endpoint[Pre])(implicit o: Origin): Expr[Post] = transitivePerm(EndpointUse[Post](succ(endpoint)), TClass(endpoint.cls)) + def variablePerm(variable: Variable[Pre]): Expr[Post] = + transitivePerm(Local[Post](succ(variable))(variable.o), variable.t)(variable.o) + + def variablesPerm(variables: Seq[Variable[Pre]])(implicit o: Origin): Expr[Post] = + foldStar(variables.map(variablePerm)) + + def resultPerm(app: ContractApplicable[Pre])(implicit o: Origin): Expr[Post] = + transitivePerm(Result[Post](anySucc(app)), app.returnType) + /* int x; From f6930d6485f9cbf791d47206143bacc02e594663 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Fri, 17 Nov 2023 12:06:07 +0100 Subject: [PATCH 15/32] Finish instance stuff, only need to reinstate seqprog now --- .../veymont/GenerateSeqProgPermissions.scala | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala index 83accbeea4..acbfb2f4ca 100644 --- a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala +++ b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala @@ -4,7 +4,7 @@ import com.typesafe.scalalogging.LazyLogging import hre.util.ScopedStack import vct.col.ast.RewriteHelpers._ import vct.col.util.AstBuildHelpers._ -import vct.col.ast.{Applicable, ApplicableContract, ArraySubscript, BooleanValue, Class, ContractApplicable, Declaration, Deref, Endpoint, EndpointUse, EnumUse, Expr, FieldLocation, Function, InstanceField, InstanceFunction, IterationContract, Length, Local, LoopContract, LoopInvariant, Null, Perm, Procedure, Result, SeqProg, SeqRun, SplitAccountedPredicate, TArray, TClass, TInt, ThisObject, Type, UnitAccountedPredicate, Variable, WritePerm} +import vct.col.ast.{Applicable, ApplicableContract, ArraySubscript, BooleanValue, Class, ContractApplicable, Declaration, Deref, Endpoint, EndpointUse, EnumUse, Expr, FieldLocation, Function, InstanceField, InstanceFunction, InstanceMethod, IterationContract, Length, Local, LoopContract, LoopInvariant, Null, Perm, Procedure, Result, SeqProg, SeqRun, SplitAccountedPredicate, TArray, TClass, TInt, ThisObject, Type, UnitAccountedPredicate, Variable, WritePerm} import vct.col.origin.{Origin, PanicBlame} import vct.col.ref.Ref import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder, RewriterBuilderArg} @@ -16,14 +16,10 @@ object GenerateSeqProgPermissions extends RewriterBuilderArg[Boolean] { case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = false) extends Rewriter[Pre] with LazyLogging { -// sealed trait Context -// case class ClassContext(cls: Class[Pre]) extends Context -// case class SeqContext(prog: SeqProg[Pre]) extends Context - val currentPerm: ScopedStack[Expr[Post]] = ScopedStack() /* - Permission generation table - - Only considered nodes so far that occur in early VeyMont case studies. + Only considers nodes as necessary for VeyMont case studies. Pre Post Invariant Function: args N.a @@ -37,7 +33,6 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals */ override def dispatch(decl: Declaration[Pre]): Unit = decl match { -// case prog: SeqProg[Pre] if enabled => currentContext.having(SeqContext(prog)) { rewriteDefault(prog) } case fun: Function[Pre] if enabled => globalDeclarations.declare(fun.rewrite( contract = prependContract(fun.contract, variablesPerm(fun.args)(fun.o), tt)(fun.o) @@ -52,9 +47,23 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals body = proc.body.map(body => currentPerm.having(variablesPerm(proc.args)) { dispatch(body) }) )) - case cls: Class[Pre] if enabled => currentPerm.having(???) { rewriteDefault(???) } + case cls: Class[Pre] if enabled => currentPerm.having(classPerm(cls)) { rewriteDefault(cls) } case fun: InstanceFunction[Pre] if enabled => + implicit val o = fun.o + classDeclarations.declare(fun.rewrite(contract = prependContract(fun.contract, currentPerm.top, tt))) + + case method: InstanceMethod[Pre] if enabled => + implicit val o = method.o + classDeclarations.declare(method.rewrite( + contract = prependContract( + method.contract, + currentPerm.top, + currentPerm.top &* resultPerm(method) + ) + )) + + case prog: SeqProg[Pre] => ??? case decl => rewriteDefault(decl) } @@ -84,11 +93,6 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals case _ => rewriteDefault(loopContract) } -// def permissions(ctx: Context)(implicit o: Origin): Expr[Post] = ctx match { -// case SeqContext(prog) => foldStar[Post](prog.endpoints.map(endpointPerm)) -// case ClassContext(cls) => transitivePerm(ThisObject[Post](succ(cls)), TClass(cls.ref)) -// } - def endpointPerm(endpoint: Endpoint[Pre])(implicit o: Origin): Expr[Post] = transitivePerm(EndpointUse[Post](succ(endpoint)), TClass(endpoint.cls)) @@ -101,6 +105,9 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals def resultPerm(app: ContractApplicable[Pre])(implicit o: Origin): Expr[Post] = transitivePerm(Result[Post](anySucc(app)), app.returnType) + def classPerm(cls: Class[Pre]): Expr[Post] = + transitivePerm(ThisObject[Post](succ(cls))(cls.o), TClass(cls.ref))(cls.o) + /* int x; From 1a51aa4be436d9000baeabc6a5d6c1ea2c0999c7 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Tue, 21 Nov 2023 11:07:40 +0100 Subject: [PATCH 16/32] Permission generation works and can only be enabled with a flag. --- src/col/vct/col/origin/Name.scala | 2 +- src/main/vct/main/stages/Resolution.scala | 4 ++- .../vct/rewrite/lang/LangPVLToCol.scala | 4 +-- .../vct/rewrite/lang/LangSpecificToCol.scala | 8 ++--- .../veymont/GenerateSeqProgPermissions.scala | 33 +++++++++++++++---- .../examples/TechnicalVeyMontSpec.scala | 31 ++++++++--------- 6 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/col/vct/col/origin/Name.scala b/src/col/vct/col/origin/Name.scala index 2fa015d4a6..52b94cbbcd 100644 --- a/src/col/vct/col/origin/Name.scala +++ b/src/col/vct/col/origin/Name.scala @@ -18,7 +18,7 @@ object Name { case class Preferred(parts: Seq[String]) extends Name { override def snake: String = parts.map(_.toLowerCase).mkString("_") override def usnake: String = parts.map(_.toUpperCase).mkString("_") - override def camel: String = (parts.head.toLowerCase +: parts.map(_.toLowerCase.capitalize)).mkString("") + override def camel: String = (parts.head.toLowerCase +: parts.tail.map(_.toLowerCase.capitalize)).mkString("") override def ucamel: String = camel.capitalize } } \ No newline at end of file diff --git a/src/main/vct/main/stages/Resolution.scala b/src/main/vct/main/stages/Resolution.scala index becb655bc9..b0f0b946d3 100644 --- a/src/main/vct/main/stages/Resolution.scala +++ b/src/main/vct/main/stages/Resolution.scala @@ -36,6 +36,7 @@ case object Resolution { case ClassPathEntry.SourcePackageRoot => ResolveTypes.JavaClassPathEntry.SourcePackageRoot case ClassPathEntry.SourcePath(root) => ResolveTypes.JavaClassPathEntry.Path(root) }, + options.veymontGeneratePermissions ) } @@ -98,6 +99,7 @@ case class Resolution[G <: Generation] ResolveTypes.JavaClassPathEntry.Path(Resources.getJrePath), ResolveTypes.JavaClassPathEntry.SourcePackageRoot ), + veymontGeneratePermissions: Boolean = false, ) extends Stage[ParseResult[G], Verification[_ <: Generation]] with LazyLogging { override def friendlyName: String = "Name Resolution" @@ -115,7 +117,7 @@ case class Resolution[G <: Generation] case Nil => // ok case some => throw InputResolutionError(some) } - val resolvedProgram = LangSpecificToCol().dispatch(typedProgram) + val resolvedProgram = LangSpecificToCol(veymontGeneratePermissions).dispatch(typedProgram) resolvedProgram.check match { case Nil => // ok // PB: This explicitly allows LangSpecificToCol to generate invalid ASTs, and will blame the input for them. The diff --git a/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala b/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala index 807b921084..491640aa78 100644 --- a/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala @@ -20,7 +20,7 @@ case object LangPVLToCol { } } -case class LangPVLToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends LazyLogging { +case class LangPVLToCol[Pre <: Generation](rw: LangSpecificToCol[Pre], veymontGeneratePermissions: Boolean) extends LazyLogging { type Post = Rewritten[Pre] implicit val implicitRewriter: AbstractRewriter[Pre, Post] = rw @@ -78,7 +78,7 @@ case class LangPVLToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends L ApplicableContract( UnitAccountedPredicate(tt), UnitAccountedPredicate(AstBuildHelpers.foldStar(cls.declarations.collect { - case field: InstanceField[Pre] if field.flags.collectFirst { case _: Final[Pre] => () }.isEmpty => + case field: InstanceField[Pre] if field.flags.collectFirst { case _: Final[Pre] => () }.isEmpty && !veymontGeneratePermissions => fieldPerm[Post](result, rw.succ(field), WritePerm()) }) &* (if (checkRunnable) IdleToken(result) else tt)), tt, Nil, Nil, Nil, None, )(TrueSatisfiable) diff --git a/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala b/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala index c386bc6755..0d1be4fe2d 100644 --- a/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala @@ -10,11 +10,11 @@ import vct.col.ref.Ref import vct.col.resolve.ctx._ import vct.col.resolve.lang.Java import vct.rewrite.lang.LangSpecificToCol.NotAValue -import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} +import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder, RewriterBuilderArg} import vct.result.VerificationError.UserError import vct.rewrite.lang.LangVeyMontToCol -case object LangSpecificToCol extends RewriterBuilder { +case object LangSpecificToCol extends RewriterBuilderArg[Boolean] { override def key: String = "langSpecific" override def desc: String = "Translate language-specific constructs to a common subset of nodes." @@ -31,12 +31,12 @@ case object LangSpecificToCol extends RewriterBuilder { } } -case class LangSpecificToCol[Pre <: Generation]() extends Rewriter[Pre] with LazyLogging { +case class LangSpecificToCol[Pre <: Generation](veymontGeneratePermissions: Boolean = false) extends Rewriter[Pre] with LazyLogging { val java: LangJavaToCol[Pre] = LangJavaToCol(this) val bip: LangBipToCol[Pre] = LangBipToCol(this) val c: LangCToCol[Pre] = LangCToCol(this) val cpp: LangCPPToCol[Pre] = LangCPPToCol(this) - val pvl: LangPVLToCol[Pre] = LangPVLToCol(this) + val pvl: LangPVLToCol[Pre] = LangPVLToCol(this, veymontGeneratePermissions) val veymont: LangVeyMontToCol[Pre] = LangVeyMontToCol(this) val silver: LangSilverToCol[Pre] = LangSilverToCol(this) val llvm: LangLLVMToCol[Pre] = LangLLVMToCol(this) diff --git a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala index acbfb2f4ca..198117449f 100644 --- a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala +++ b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala @@ -9,12 +9,15 @@ import vct.col.origin.{Origin, PanicBlame} import vct.col.ref.Ref import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder, RewriterBuilderArg} +import scala.annotation.switch + object GenerateSeqProgPermissions extends RewriterBuilderArg[Boolean] { override def key: String = "generateSeqProgPermissions" override def desc: String = "Generates permissions for fields of some types (classes, int, bool, and arrays of these) for constructs used inside seq_program." } case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = false) extends Rewriter[Pre] with LazyLogging { + println(s"Enabled: $enabled") val currentPerm: ScopedStack[Expr[Post]] = ScopedStack() @@ -34,12 +37,12 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals override def dispatch(decl: Declaration[Pre]): Unit = decl match { case fun: Function[Pre] if enabled => - globalDeclarations.declare(fun.rewrite( + globalDeclarations.succeed(fun, fun.rewrite( contract = prependContract(fun.contract, variablesPerm(fun.args)(fun.o), tt)(fun.o) )) case proc: Procedure[Pre] if enabled => implicit val o = proc.o - globalDeclarations.declare(proc.rewrite( + globalDeclarations.succeed(proc, proc.rewrite( contract = prependContract( proc.contract, variablesPerm(proc.args), @@ -51,11 +54,11 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals case fun: InstanceFunction[Pre] if enabled => implicit val o = fun.o - classDeclarations.declare(fun.rewrite(contract = prependContract(fun.contract, currentPerm.top, tt))) + classDeclarations.succeed(fun, fun.rewrite(contract = prependContract(fun.contract, currentPerm.top, tt))) case method: InstanceMethod[Pre] if enabled => implicit val o = method.o - classDeclarations.declare(method.rewrite( + classDeclarations.succeed(method, method.rewrite( contract = prependContract( method.contract, currentPerm.top, @@ -63,7 +66,23 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals ) )) - case prog: SeqProg[Pre] => ??? + case prog: SeqProg[Pre] if enabled => + val run = prog.run + globalDeclarations.succeed(prog, prog.rewrite( + contract = prependContract( + prog.contract, + variablesPerm(prog.args)(prog.o), + variablesPerm(prog.args)(prog.o) + )(prog.o), + run = run.rewrite( + contract = prependContract( + run.contract, + endpointsPerm(prog.endpoints)(run.o), + endpointsPerm(prog.endpoints)(run.o), + )(run.o), + body = currentPerm.having(endpointsPerm(prog.endpoints)(run.o)) { + rewriteDefault(run.body) + }))) case decl => rewriteDefault(decl) } @@ -96,6 +115,9 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals def endpointPerm(endpoint: Endpoint[Pre])(implicit o: Origin): Expr[Post] = transitivePerm(EndpointUse[Post](succ(endpoint)), TClass(endpoint.cls)) + def endpointsPerm(endpoints: Seq[Endpoint[Pre]])(implicit o: Origin): Expr[Post] = + foldStar(endpoints.map(endpointPerm)) + def variablePerm(variable: Variable[Pre]): Expr[Post] = transitivePerm(Local[Post](succ(variable))(variable.o), variable.t)(variable.o) @@ -144,5 +166,4 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals fieldPerm[Post](`this`, succ(f), WritePerm()) &* transitivePerm(Deref[Post](`this`, succ(f))(PanicBlame("Permission for this field is already established")), f.t) } - } diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index 872aa9f987..ddab1c47c2 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -481,28 +481,25 @@ class TechnicalVeyMontSpec extends VercorsSpec { } """ -} - -class TechnicalVeyMontSpec2 extends VercorsSpec { (vercors should verify using silicon flags Seq("--veymont-generate-permissions") - in "Loops should also limit the number of participants when combined with branches" pvl - """ - class Storage { - int x; + in "Permission should be generated for constructors as well" pvl + """ + class Storage { + int x; - int m() { - x = 2; + int m() { + x = 2; + } } - } - seq_program Example() { - endpoint alice = Storage(); - seq_run { - alice.m(); - assert alice.x == 2; + seq_program Example() { + endpoint alice = Storage(); + seq_run { + alice.m(); + assert alice.x == 2; + } } - } - """) + """) } \ No newline at end of file From a68713090db724c47e0052970a25b91e07e82052 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Tue, 21 Nov 2023 11:42:42 +0100 Subject: [PATCH 17/32] Write blame boilerplate --- src/col/vct/col/ast/Node.scala | 8 ++-- src/col/vct/col/origin/Blame.scala | 6 +++ .../vct/col/typerules/CoercingRewriter.scala | 12 +++--- src/parsers/antlr4/LangPVLParser.g4 | 2 +- .../vct/parsers/transform/PVLToCol.scala | 8 ++-- .../vct/rewrite/lang/LangSpecificToCol.scala | 2 +- .../vct/rewrite/lang/LangVeyMontToCol.scala | 6 +-- .../examples/TechnicalVeyMontSpec.scala | 37 ++++++++++++++++++- 8 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index a375b62e3c..a05beb09fe 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -1271,8 +1271,8 @@ case class PVLFamilyRange[G](family: String, binder: String, start: Expr[G], end case class PVLCommunicateAccess[G](subject: PVLCommunicateSubject[G], field: String)(implicit val o: Origin) extends NodeFamily[G] with PVLCommunicateAccessImpl[G] { var ref: Option[RefField[G]] = None } -case class PVLCommunicate[G](sender: PVLCommunicateAccess[G], receiver: PVLCommunicateAccess[G])(implicit val o: Origin) extends Statement[G] with PVLCommunicateImpl[G] -final case class PVLSeqAssign[G](receiver: Ref[G, PVLEndpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(implicit val o: Origin) extends Statement[G] with PVLSeqAssignImpl[G] +case class PVLCommunicate[G](sender: PVLCommunicateAccess[G], receiver: PVLCommunicateAccess[G])(val blame: Blame[PVLCommunicateFailure])(implicit val o: Origin) extends Statement[G] with PVLCommunicateImpl[G] +final case class PVLSeqAssign[G](receiver: Ref[G, PVLEndpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(val blame: Blame[PVLSeqAssignFailure])(implicit val o: Origin) extends Statement[G] with PVLSeqAssignImpl[G] final class Endpoint[G](val cls: Ref[G, Class[G]], val constructor: Ref[G, Procedure[G]], val args: Seq[Expr[G]])(implicit val o: Origin) extends Declaration[G] with EndpointImpl[G] final class SeqProg[G](val contract: ApplicableContract[G], val args : Seq[Variable[G]], val endpoints: Seq[Endpoint[G]], val run: SeqRun[G], val decls: Seq[ClassDeclaration[G]])(implicit val o: Origin) extends GlobalDeclaration[G] with SeqProgImpl[G] @@ -1280,8 +1280,8 @@ final case class SeqRun[G](body: Statement[G], contract: ApplicableContract[G])( sealed trait Subject[G] extends NodeFamily[G] final case class EndpointName[G](ref: Ref[G, Endpoint[G]])(implicit val o: Origin) extends Subject[G] with EndpointNameImpl[G] case class Access[G](subject: Subject[G], field: Ref[G, InstanceField[G]])(implicit val o: Origin) extends NodeFamily[G] with AccessImpl[G] -final case class Communicate[G](receiver: Access[G], sender: Access[G])(implicit val o: Origin) extends Statement[G] with CommunicateImpl[G] -final case class SeqAssign[G](receiver: Ref[G, Endpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(implicit val o: Origin) extends Statement[G] with SeqAssignImpl[G] +final case class Communicate[G](receiver: Access[G], sender: Access[G])(val blame: Blame[CommunicateFailure])(implicit val o: Origin) extends Statement[G] with CommunicateImpl[G] +final case class SeqAssign[G](receiver: Ref[G, Endpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(val blame: Blame[SeqAssignFailure])(implicit val o: Origin) extends Statement[G] with SeqAssignImpl[G] final case class EndpointUse[G](ref: Ref[G, Endpoint[G]])(implicit val o: Origin) extends Expr[G] with EndpointUseImpl[G] final case class UnresolvedSeqBranch[G](branches: Seq[(Expr[G], Statement[G])])(val blame: Blame[SeqBranchFailure])(implicit val o: Origin) extends Statement[G] diff --git a/src/col/vct/col/origin/Blame.scala b/src/col/vct/col/origin/Blame.scala index 708f19d5c8..8d0af74307 100644 --- a/src/col/vct/col/origin/Blame.scala +++ b/src/col/vct/col/origin/Blame.scala @@ -347,6 +347,12 @@ case class LoopUnanimityNotMaintained(guard1: Node[_], guard2: Node[_]) extends override def inlineDesc: String = "The agreement of two conditions in this branch could not be maintained for an arbitrary loop iteration." } +sealed trait PVLCommunicateFailure extends VerificationFailure +sealed trait CommunicateFailure extends PVLCommunicateFailure + +sealed trait PVLSeqAssignFailure extends VerificationFailure +sealed trait SeqAssignFailure extends PVLSeqAssignFailure + sealed trait DerefInsufficientPermission extends FrontendDerefError case class InsufficientPermission(node: HeapDeref[_]) extends DerefInsufficientPermission with NodeVerificationFailure { override def code: String = "perm" diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index 38e2c6b003..641e818e2f 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -1683,18 +1683,18 @@ abstract class CoercingRewriter[Pre <: Generation]() extends AbstractRewriter[Pr case w @ WandPackage(expr, stat) => WandPackage(res(expr), stat)(w.blame) case VeyMontAssignExpression(t,a) => VeyMontAssignExpression(t,a) case CommunicateX(r,s,t,a) => CommunicateX(r,s,t,a) - case PVLCommunicate(s, r) if r.fieldType == s.fieldType => PVLCommunicate(s, r) + case c @ PVLCommunicate(s, r) if r.fieldType == s.fieldType => PVLCommunicate(s, r)(c.blame) case comm@PVLCommunicate(s, r) => throw IncoercibleExplanation(comm, s"The receiver should have type ${s.fieldType}, but actually has type ${r.fieldType}.") - case Communicate(r, s) if r.field.decl.t == s.field.decl.t => Communicate(r, s) + case c @ Communicate(r, s) if r.field.decl.t == s.field.decl.t => Communicate(r, s)(c.blame) case comm@Communicate(r, s) => throw IncoercibleExplanation(comm, s"The receiver should have type ${s.field.decl.t}, but actually has type ${r.field.decl.t}.") - case PVLSeqAssign(r, f, v) => - try { PVLSeqAssign(r, f, coerce(v, f.decl.t)) } catch { + case a @ PVLSeqAssign(r, f, v) => + try { PVLSeqAssign(r, f, coerce(v, f.decl.t))(a.blame) } catch { case err: Incoercible => println(err.text) throw err } - case SeqAssign(r, f, v) => - try { SeqAssign(r, f, coerce(v, f.decl.t)) } catch { + case a @ SeqAssign(r, f, v) => + try { SeqAssign(r, f, coerce(v, f.decl.t))(a.blame) } catch { case err: Incoercible => println(err.text) throw err diff --git a/src/parsers/antlr4/LangPVLParser.g4 b/src/parsers/antlr4/LangPVLParser.g4 index 2b3b84ddce..d061e89d8f 100644 --- a/src/parsers/antlr4/LangPVLParser.g4 +++ b/src/parsers/antlr4/LangPVLParser.g4 @@ -188,7 +188,7 @@ statement | 'label' identifier ';' # pvlLabel | allowedForStatement ';' # pvlForStatement | 'communicate' access direction access ';' # pvlCommunicateStatement - | identifier '.' identifier ':' '=' expr ';' # pvlParAssign + | identifier '.' identifier ':' '=' expr ';' # pvlSeqAssign ; direction diff --git a/src/parsers/vct/parsers/transform/PVLToCol.scala b/src/parsers/vct/parsers/transform/PVLToCol.scala index e8ee23565a..91702bd3b4 100644 --- a/src/parsers/vct/parsers/transform/PVLToCol.scala +++ b/src/parsers/vct/parsers/transform/PVLToCol.scala @@ -357,14 +357,14 @@ case class PVLToCol[G](override val baseOrigin: Origin, case PvlLabel(_, label, _) => Label(new LabelDecl()(origin(stat).sourceName(convert(label))), Block(Nil)) case PvlForStatement(inner, _) => convert(inner) case PvlCommunicateStatement(_, receiver, Direction0("<-"), sender, _) => - PVLCommunicate(convert(sender), convert(receiver)) + PVLCommunicate(convert(sender), convert(receiver))(blame(stat)) case PvlCommunicateStatement(_, sender, Direction1("->"), receiver, _) => - PVLCommunicate(convert(sender), convert(receiver)) - case PvlParAssign(endpoint, _, field, _, _, expr, _) => + PVLCommunicate(convert(sender), convert(receiver))(blame(stat)) + case PvlSeqAssign(endpoint, _, field, _, _, expr, _) => PVLSeqAssign( new UnresolvedRef[G, PVLEndpoint[G]](convert(endpoint)), new UnresolvedRef[G, InstanceField[G]](convert(field)), - convert(expr)) + convert(expr))(blame(stat)) } def convert(implicit stat: ForStatementListContext): Statement[G] = diff --git a/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala b/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala index 0d1be4fe2d..d861f050c1 100644 --- a/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala @@ -172,7 +172,7 @@ case class LangSpecificToCol[Pre <: Generation](veymontGeneratePermissions: Bool case eval@Eval(CPPInvocation(_, _, _, _)) => cpp.invocationStatement(eval) case communicate: PVLCommunicate[Pre] => veymont.rewriteCommunicate(communicate) - case assign: PVLSeqAssign[Pre] => veymont.rewriteParAssign(assign) + case assign: PVLSeqAssign[Pre] => veymont.rewriteSeqAssign(assign) case other => rewriteDefault(other) } diff --git a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala index c2766538a7..c50a3f24bb 100644 --- a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala @@ -38,7 +38,7 @@ case class LangVeyMontToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) exten val currentProg: ScopedStack[PVLSeqProg[Pre]] = ScopedStack() def rewriteCommunicate(comm: PVLCommunicate[Pre]): Communicate[Post] = - Communicate(rewriteAccess(comm.receiver), rewriteAccess(comm.sender))(comm.o) + Communicate(rewriteAccess(comm.receiver), rewriteAccess(comm.sender))(comm.blame)(comm.o) def rewriteAccess(access: PVLCommunicateAccess[Pre]): Access[Post] = Access[Post](rewriteSubject(access.subject), rw.succ(access.ref.get.decl))(access.o) @@ -94,8 +94,8 @@ case class LangVeyMontToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) exten SeqRun(rw.dispatch(run.body), rw.dispatch(run.contract))(run.blame)(run.o) } - def rewriteParAssign(assign: PVLSeqAssign[Pre]): SeqAssign[Post] = - SeqAssign[Post](endpointSucc.ref(assign.receiver.decl), rw.succ(assign.field.decl), rw.dispatch(assign.value))(assign.o) + def rewriteSeqAssign(assign: PVLSeqAssign[Pre]): SeqAssign[Post] = + SeqAssign[Post](endpointSucc.ref(assign.receiver.decl), rw.succ(assign.field.decl), rw.dispatch(assign.value))(assign.blame)(assign.o) def rewriteBranch(branch: PVLBranch[Pre]): UnresolvedSeqBranch[Post] = UnresolvedSeqBranch(branch.branches.map { case (e, s) => (rw.dispatch(e), rw.dispatch(s)) })(branch.blame)(branch.o) diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index ddab1c47c2..1606c02832 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -502,4 +502,39 @@ class TechnicalVeyMontSpec extends VercorsSpec { } } """) -} \ No newline at end of file +} + +class TechnicalVeyMontSpec2 extends VercorsSpec { + (vercors should verify + using silicon + in "Permission should be generated for constructors as well" pvl + """ + class Storage { + int x; + } + + seq_program Example() { + endpoint alice = Storage(); + seq_run { + alice.x := 2; + } + } + """) + + (vercors should verify + using silicon + in "Permission should be generated for constructors all" pvl + """ + class Storage { + int x; + } + + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + seq_run { + communicate alice.x <- bob.x; + } + } + """) +} From aa9417aa0d04f415f203815a26bd4e64f8e914c8 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Tue, 21 Nov 2023 17:43:20 +0100 Subject: [PATCH 18/32] Make blames a bit more concrete. Or should they be on the accesses? --- src/col/vct/col/origin/Blame.scala | 6 +++++ .../vct/rewrite/veymont/EncodeSeqProg.scala | 26 ++++++++++++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/col/vct/col/origin/Blame.scala b/src/col/vct/col/origin/Blame.scala index 8d0af74307..0edcb7aa9b 100644 --- a/src/col/vct/col/origin/Blame.scala +++ b/src/col/vct/col/origin/Blame.scala @@ -350,6 +350,12 @@ case class LoopUnanimityNotMaintained(guard1: Node[_], guard2: Node[_]) extends sealed trait PVLCommunicateFailure extends VerificationFailure sealed trait CommunicateFailure extends PVLCommunicateFailure +case class CommunicateTargetPermission(node: Access[_]) extends CommunicateFailure with NodeVerificationFailure { + override def code: String = "communicateTargetPermission" + override def descInContext: String = "There may be insufficient permission to access this field." + override def inlineDescWithSource(source: String): String = s"There may be insufficient permission to access `$source`." +} + sealed trait PVLSeqAssignFailure extends VerificationFailure sealed trait SeqAssignFailure extends PVLSeqAssignFailure diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala index bb164eda7f..11e9b9bf3f 100644 --- a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala @@ -3,7 +3,7 @@ package vct.rewrite.veymont import com.typesafe.scalalogging.LazyLogging import hre.util.ScopedStack import vct.col.ast.{Access, Assign, Block, Class, Communicate, Declaration, Deref, Endpoint, EndpointName, EndpointUse, Eval, Expr, Local, LocalDecl, Node, Procedure, Scope, SeqAssign, SeqProg, SeqRun, Statement, Subject, TClass, TVoid, Variable} -import vct.col.origin.{Blame, CallableFailure, DiagnosticOrigin, Origin, PanicBlame, VerificationFailure} +import vct.col.origin.{AssignFailed, Blame, CallableFailure, CommunicateFailure, CommunicateTargetPermission, DiagnosticOrigin, InsufficientPermission, Origin, PanicBlame, VerificationFailure} import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} import vct.col.util.AstBuildHelpers._ import vct.col.util.SuccessionMap @@ -140,6 +140,18 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog } } + case class ForwardAssignFailed(access: Access[_], blame: Blame[CommunicateFailure]) extends Blame[AssignFailed] { + override def blame(error: AssignFailed): Unit = { + blame.blame(CommunicateTargetPermission(access)) + } + } + + case class ForwardInsufficientPermission(access: Access[_], blame: Blame[CommunicateFailure]) extends Blame[InsufficientPermission] { + override def blame(error: InsufficientPermission): Unit = { + blame.blame(CommunicateTargetPermission(access)) + } + } + override def dispatch(stat: Statement[Pre]): Statement[Post] = stat match { case assign@SeqAssign(Ref(endpoint), Ref(field), e) => implicit val o = assign.o @@ -150,17 +162,17 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog )(GeneratedPerm), dispatch(e) )(GeneratedPerm) - case comm@Communicate(receiver, sender) => + case comm @ Communicate(receiver, sender) => implicit val o = comm.o Assign[Post]( - rewriteAccess(receiver), - rewriteAccess(sender) - )(GeneratedPerm) + rewriteAccess(receiver, PanicBlame("This one should not be used.")), + rewriteAccess(sender, comm.blame) + )(ForwardAssignFailed(receiver, comm.blame)) case stat => rewriteDefault(stat) } - def rewriteAccess(access: Access[Pre]): Expr[Post] = - Deref[Post](rewriteSubject(access.subject), succ(access.field.decl))(GeneratedPerm)(access.o) + def rewriteAccess(access: Access[Pre], blame: Blame[CommunicateFailure]): Expr[Post] = + Deref[Post](rewriteSubject(access.subject), succ(access.field.decl))(ForwardInsufficientPermission(access, blame))(access.o) def rewriteSubject(subject: Subject[Pre]): Expr[Post] = subject match { case EndpointName(Ref(endpoint)) => Local[Post](endpointSucc((mode, endpoint)).ref)(subject.o) From eb675dee43aadefdb877fcde0dc4e66f9a42a102 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 22 Nov 2023 10:49:25 +0100 Subject: [PATCH 19/32] Rename without Communicate --- src/col/vct/col/ast/Node.scala | 12 +++++------ .../PVLCommunicateAccessImpl.scala | 4 ++-- .../PVLCommunicateSubjectImpl.scala | 4 ++-- src/col/vct/col/resolve/Resolve.scala | 2 +- src/col/vct/col/resolve/lang/Java.scala | 4 ++++ .../vct/col/rewrite/NonLatchingRewriter.scala | 4 ++-- .../vct/col/typerules/CoercingRewriter.scala | 20 +++++++++---------- .../vct/parsers/transform/PVLToCol.scala | 6 +++--- .../vct/rewrite/lang/LangVeyMontToCol.scala | 4 ++-- 9 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index a05beb09fe..4a352a1815 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -1258,20 +1258,20 @@ final class PVLEndpoint[G](val name: String, val cls: Ref[G, Class[G]], val args final class PVLSeqProg[G](val name: String, val declarations: Seq[ClassDeclaration[G]], val contract: ApplicableContract[G], val args: Seq[Variable[G]])(implicit val o: Origin) extends GlobalDeclaration[G] with PVLSeqProgImpl[G] with Declarator[G] final case class PVLSeqRun[G](body: Statement[G], contract: ApplicableContract[G])(val blame: Blame[CallableFailure])(implicit val o: Origin) extends ClassDeclaration[G] -sealed trait PVLCommunicateSubject[G] extends NodeFamily[G] with PVLCommunicateSubjectImpl[G] -case class PVLEndpointName[G](name: String)(implicit val o: Origin) extends PVLCommunicateSubject[G] with PVLEndpointNameImpl[G] { +sealed trait PVLSubject[G] extends NodeFamily[G] with PVLCommunicateSubjectImpl[G] +case class PVLEndpointName[G](name: String)(implicit val o: Origin) extends PVLSubject[G] with PVLEndpointNameImpl[G] { var ref: Option[RefPVLEndpoint[G]] = None } -case class PVLIndexedFamilyName[G](family: String, index: Expr[G])(implicit val o: Origin) extends PVLCommunicateSubject[G] with PVLIndexedFamilyNameImpl[G] { +case class PVLIndexedFamilyName[G](family: String, index: Expr[G])(implicit val o: Origin) extends PVLSubject[G] with PVLIndexedFamilyNameImpl[G] { var ref: Option[RefPVLEndpoint[G]] = None } -case class PVLFamilyRange[G](family: String, binder: String, start: Expr[G], end: Expr[G])(implicit val o: Origin) extends PVLCommunicateSubject[G] with PVLFamilyRangeImpl[G] { +case class PVLFamilyRange[G](family: String, binder: String, start: Expr[G], end: Expr[G])(implicit val o: Origin) extends PVLSubject[G] with PVLFamilyRangeImpl[G] { var ref: Option[RefPVLEndpoint[G]] = None } -case class PVLCommunicateAccess[G](subject: PVLCommunicateSubject[G], field: String)(implicit val o: Origin) extends NodeFamily[G] with PVLCommunicateAccessImpl[G] { +case class PVLAccess[G](subject: PVLSubject[G], field: String)(implicit val o: Origin) extends NodeFamily[G] with PVLCommunicateAccessImpl[G] { var ref: Option[RefField[G]] = None } -case class PVLCommunicate[G](sender: PVLCommunicateAccess[G], receiver: PVLCommunicateAccess[G])(val blame: Blame[PVLCommunicateFailure])(implicit val o: Origin) extends Statement[G] with PVLCommunicateImpl[G] +case class PVLCommunicate[G](sender: PVLAccess[G], receiver: PVLAccess[G])(val blame: Blame[PVLCommunicateFailure])(implicit val o: Origin) extends Statement[G] with PVLCommunicateImpl[G] final case class PVLSeqAssign[G](receiver: Ref[G, PVLEndpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(val blame: Blame[PVLSeqAssignFailure])(implicit val o: Origin) extends Statement[G] with PVLSeqAssignImpl[G] final class Endpoint[G](val cls: Ref[G, Class[G]], val constructor: Ref[G, Procedure[G]], val args: Seq[Expr[G]])(implicit val o: Origin) extends Declaration[G] with EndpointImpl[G] diff --git a/src/col/vct/col/ast/family/pvlcommunicate/PVLCommunicateAccessImpl.scala b/src/col/vct/col/ast/family/pvlcommunicate/PVLCommunicateAccessImpl.scala index 82d9bf5dbe..7126a7642c 100644 --- a/src/col/vct/col/ast/family/pvlcommunicate/PVLCommunicateAccessImpl.scala +++ b/src/col/vct/col/ast/family/pvlcommunicate/PVLCommunicateAccessImpl.scala @@ -1,7 +1,7 @@ package vct.col.ast.family.pvlcommunicate -import vct.col.ast.{PVLCommunicateAccess, Type} +import vct.col.ast.{PVLAccess, Type} -trait PVLCommunicateAccessImpl[G] { this: PVLCommunicateAccess[G] => +trait PVLCommunicateAccessImpl[G] { this: PVLAccess[G] => def fieldType: Type[G] = ref.get.decl.t } diff --git a/src/col/vct/col/ast/family/pvlcommunicate/PVLCommunicateSubjectImpl.scala b/src/col/vct/col/ast/family/pvlcommunicate/PVLCommunicateSubjectImpl.scala index 88a12964b7..b6aa7d0eb3 100644 --- a/src/col/vct/col/ast/family/pvlcommunicate/PVLCommunicateSubjectImpl.scala +++ b/src/col/vct/col/ast/family/pvlcommunicate/PVLCommunicateSubjectImpl.scala @@ -1,9 +1,9 @@ package vct.col.ast.family.pvlcommunicate -import vct.col.ast.{PVLCommunicateSubject, TClass, Class, Type} +import vct.col.ast.{PVLSubject, TClass, Class, Type} import vct.col.resolve.ctx.RefPVLEndpoint -trait PVLCommunicateSubjectImpl[G] { this: PVLCommunicateSubject[G] => +trait PVLCommunicateSubjectImpl[G] { this: PVLSubject[G] => def cls: Class[G] = ref.get.decl.cls.decl def ref: Option[RefPVLEndpoint[G]] } diff --git a/src/col/vct/col/resolve/Resolve.scala b/src/col/vct/col/resolve/Resolve.scala index b0b8288015..52d02425da 100644 --- a/src/col/vct/col/resolve/Resolve.scala +++ b/src/col/vct/col/resolve/Resolve.scala @@ -404,7 +404,7 @@ case object ResolveReferences extends LazyLogging { case Some(_) => throw ForbiddenEndpointNameType(local) case None => throw NoSuchNameError("endpoint", name, local) } - case access@PVLCommunicateAccess(subject, field) => + case access@PVLAccess(subject, field) => access.ref = Some(PVL.findDerefOfClass(subject.cls, field).getOrElse(throw NoSuchNameError("field", field, access))) case endpoint: PVLEndpoint[G] => endpoint.ref = Some(PVL.findConstructor(TClass(endpoint.cls.decl.ref[Class[G]]), endpoint.args).getOrElse(throw ConstructorNotFound(endpoint))) diff --git a/src/col/vct/col/resolve/lang/Java.scala b/src/col/vct/col/resolve/lang/Java.scala index 80bbf5243d..b94540385b 100644 --- a/src/col/vct/col/resolve/lang/Java.scala +++ b/src/col/vct/col/resolve/lang/Java.scala @@ -241,6 +241,10 @@ case object Java extends LazyLogging { case JavaClassPathEntry.Path(root) => Some(root) } + println(s"Resolution: $name") + println(s"maybeBasePath: $maybeBasePath") + println(s"classPath: ${ctx.javaClassPath}") + for { basePath <- maybeBasePath ns <- loader.load[G](basePath, name) diff --git a/src/col/vct/col/rewrite/NonLatchingRewriter.scala b/src/col/vct/col/rewrite/NonLatchingRewriter.scala index a79c5ceab9..a5ec0bee03 100644 --- a/src/col/vct/col/rewrite/NonLatchingRewriter.scala +++ b/src/col/vct/col/rewrite/NonLatchingRewriter.scala @@ -66,8 +66,8 @@ class NonLatchingRewriter[Pre, Post]() extends AbstractRewriter[Pre, Post] { override def dispatch(node: LlvmFunctionContract[Pre]): LlvmFunctionContract[Post] = rewriteDefault(node) override def dispatch(node: LlvmLoopContract[Pre]): LlvmLoopContract[Post] = rewriteDefault(node) - override def dispatch(node: PVLCommunicateAccess[Pre]): PVLCommunicateAccess[Post] = rewriteDefault(node) - override def dispatch(node: PVLCommunicateSubject[Pre]): PVLCommunicateSubject[Post] = rewriteDefault(node) + override def dispatch(node: PVLAccess[Pre]): PVLAccess[Post] = rewriteDefault(node) + override def dispatch(node: PVLSubject[Pre]): PVLSubject[Post] = rewriteDefault(node) override def dispatch(node: SeqRun[Pre]): SeqRun[Post] = rewriteDefault(node) override def dispatch(node: Access[Pre]): Access[Post] = rewriteDefault(node) override def dispatch(node: Subject[Pre]): Subject[Post] = rewriteDefault(node) diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index 641e818e2f..40bf86fe77 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -254,8 +254,8 @@ abstract class CoercingRewriter[Pre <: Generation]() extends AbstractRewriter[Pr case node: LlvmLoopContract[Pre] => node case node: ProverLanguage[Pre] => node case node: SmtlibFunctionSymbol[Pre] => node - case node: PVLCommunicateAccess[Pre] => node - case node: PVLCommunicateSubject[Pre] => node + case node: PVLAccess[Pre] => node + case node: PVLSubject[Pre] => node case node: SeqRun[Pre] => node case node: Access[Pre] => node case node: Subject[Pre] => node @@ -462,13 +462,13 @@ abstract class CoercingRewriter[Pre <: Generation]() extends AbstractRewriter[Pr def postCoerce(node: SmtlibFunctionSymbol[Pre]): SmtlibFunctionSymbol[Post] = rewriteDefault(node) override final def dispatch(node: SmtlibFunctionSymbol[Pre]): SmtlibFunctionSymbol[Post] = postCoerce(coerce(preCoerce(node))) - def preCoerce(node: PVLCommunicateAccess[Pre]): PVLCommunicateAccess[Pre] = node - def postCoerce(node: PVLCommunicateAccess[Pre]): PVLCommunicateAccess[Post] = rewriteDefault(node) - override final def dispatch(node: PVLCommunicateAccess[Pre]): PVLCommunicateAccess[Post] = postCoerce(coerce(preCoerce(node))) + def preCoerce(node: PVLAccess[Pre]): PVLAccess[Pre] = node + def postCoerce(node: PVLAccess[Pre]): PVLAccess[Post] = rewriteDefault(node) + override final def dispatch(node: PVLAccess[Pre]): PVLAccess[Post] = postCoerce(coerce(preCoerce(node))) - def preCoerce(node: PVLCommunicateSubject[Pre]): PVLCommunicateSubject[Pre] = node - def postCoerce(node: PVLCommunicateSubject[Pre]): PVLCommunicateSubject[Post] = rewriteDefault(node) - override final def dispatch(node: PVLCommunicateSubject[Pre]): PVLCommunicateSubject[Post] = postCoerce(coerce(preCoerce(node))) + def preCoerce(node: PVLSubject[Pre]): PVLSubject[Pre] = node + def postCoerce(node: PVLSubject[Pre]): PVLSubject[Post] = rewriteDefault(node) + override final def dispatch(node: PVLSubject[Pre]): PVLSubject[Post] = postCoerce(coerce(preCoerce(node))) def preCoerce(node: Access[Pre]): Access[Pre] = node def postCoerce(node: Access[Pre]): Access[Post] = rewriteDefault(node) @@ -2186,8 +2186,8 @@ abstract class CoercingRewriter[Pre <: Generation]() extends AbstractRewriter[Pr def coerce(node: ProverLanguage[Pre]): ProverLanguage[Pre] = node def coerce(node: SmtlibFunctionSymbol[Pre]): SmtlibFunctionSymbol[Pre] = node - def coerce(node: PVLCommunicateAccess[Pre]): PVLCommunicateAccess[Pre] = node - def coerce(node: PVLCommunicateSubject[Pre]): PVLCommunicateSubject[Pre] = node + def coerce(node: PVLAccess[Pre]): PVLAccess[Pre] = node + def coerce(node: PVLSubject[Pre]): PVLSubject[Pre] = node def coerce(node: SeqRun[Pre]): SeqRun[Pre] = node def coerce(node: Access[Pre]): Access[Pre] = node def coerce(node: Subject[Pre]): Subject[Pre] = node diff --git a/src/parsers/vct/parsers/transform/PVLToCol.scala b/src/parsers/vct/parsers/transform/PVLToCol.scala index 91702bd3b4..9afca05794 100644 --- a/src/parsers/vct/parsers/transform/PVLToCol.scala +++ b/src/parsers/vct/parsers/transform/PVLToCol.scala @@ -388,11 +388,11 @@ case class PVLToCol[G](override val baseOrigin: Origin, case PvlAssign(target, _, value) => Assign(convert(target), convert(value))(blame(stat)) } - def convert(implicit acc: AccessContext): PVLCommunicateAccess[G] = acc match { - case Access0(subject, _, field) => PVLCommunicateAccess(convert(subject), convert(field)) + def convert(implicit acc: AccessContext): PVLAccess[G] = acc match { + case Access0(subject, _, field) => PVLAccess(convert(subject), convert(field)) } - def convert(implicit subject: SubjectContext): PVLCommunicateSubject[G] = subject match { + def convert(implicit subject: SubjectContext): PVLSubject[G] = subject match { case Subject0(name) => PVLEndpointName(convert(name))(origin(subject).sourceName(convert(name))) case Subject1(family, _, expr, _) => ??(subject) case Subject2(family, _, binder, _, start, _, end, _) => ??(subject) diff --git a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala index c50a3f24bb..8076d6d706 100644 --- a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala @@ -40,10 +40,10 @@ case class LangVeyMontToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) exten def rewriteCommunicate(comm: PVLCommunicate[Pre]): Communicate[Post] = Communicate(rewriteAccess(comm.receiver), rewriteAccess(comm.sender))(comm.blame)(comm.o) - def rewriteAccess(access: PVLCommunicateAccess[Pre]): Access[Post] = + def rewriteAccess(access: PVLAccess[Pre]): Access[Post] = Access[Post](rewriteSubject(access.subject), rw.succ(access.ref.get.decl))(access.o) - def rewriteSubject(subject: PVLCommunicateSubject[Pre]): Subject[Post] = subject match { + def rewriteSubject(subject: PVLSubject[Pre]): Subject[Post] = subject match { case subject@PVLEndpointName(name) => EndpointName[Post](endpointSucc.ref(subject.ref.get.decl))(subject.o) case PVLIndexedFamilyName(family, index) => ??? case PVLFamilyRange(family, binder, start, end) => ??? From 5661056a0b54c8fa3c1a3b030755b6946c922d62 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 22 Nov 2023 10:54:14 +0100 Subject: [PATCH 20/32] Rename impls --- src/col/vct/col/ast/Node.scala | 6 +++--- .../{PVLCommunicateAccessImpl.scala => PVLAccessImpl.scala} | 2 +- ...PVLCommunicateSubjectImpl.scala => PVLSubjectImpl.scala} | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/col/vct/col/ast/family/pvlcommunicate/{PVLCommunicateAccessImpl.scala => PVLAccessImpl.scala} (68%) rename src/col/vct/col/ast/family/pvlcommunicate/{PVLCommunicateSubjectImpl.scala => PVLSubjectImpl.scala} (78%) diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index 4a352a1815..1447fc9d50 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -55,7 +55,7 @@ import vct.col.ast.family.javavar.JavaVariableDeclarationImpl import vct.col.ast.family.location._ import vct.col.ast.family.loopcontract._ import vct.col.ast.family.parregion._ -import vct.col.ast.family.pvlcommunicate.{PVLCommunicateAccessImpl, PVLCommunicateImpl, PVLCommunicateSubjectImpl, PVLEndpointNameImpl, PVLFamilyRangeImpl, PVLIndexedFamilyNameImpl} +import vct.col.ast.family.pvlcommunicate.{PVLAccessImpl, PVLCommunicateImpl, PVLSubjectImpl, PVLEndpointNameImpl, PVLFamilyRangeImpl, PVLIndexedFamilyNameImpl} import vct.col.ast.family.seqguard._ import vct.col.ast.family.seqrun.SeqRunImpl import vct.col.ast.family.signals._ @@ -1258,7 +1258,7 @@ final class PVLEndpoint[G](val name: String, val cls: Ref[G, Class[G]], val args final class PVLSeqProg[G](val name: String, val declarations: Seq[ClassDeclaration[G]], val contract: ApplicableContract[G], val args: Seq[Variable[G]])(implicit val o: Origin) extends GlobalDeclaration[G] with PVLSeqProgImpl[G] with Declarator[G] final case class PVLSeqRun[G](body: Statement[G], contract: ApplicableContract[G])(val blame: Blame[CallableFailure])(implicit val o: Origin) extends ClassDeclaration[G] -sealed trait PVLSubject[G] extends NodeFamily[G] with PVLCommunicateSubjectImpl[G] +sealed trait PVLSubject[G] extends NodeFamily[G] with PVLSubjectImpl[G] case class PVLEndpointName[G](name: String)(implicit val o: Origin) extends PVLSubject[G] with PVLEndpointNameImpl[G] { var ref: Option[RefPVLEndpoint[G]] = None } @@ -1268,7 +1268,7 @@ case class PVLIndexedFamilyName[G](family: String, index: Expr[G])(implicit val case class PVLFamilyRange[G](family: String, binder: String, start: Expr[G], end: Expr[G])(implicit val o: Origin) extends PVLSubject[G] with PVLFamilyRangeImpl[G] { var ref: Option[RefPVLEndpoint[G]] = None } -case class PVLAccess[G](subject: PVLSubject[G], field: String)(implicit val o: Origin) extends NodeFamily[G] with PVLCommunicateAccessImpl[G] { +case class PVLAccess[G](subject: PVLSubject[G], field: String)(implicit val o: Origin) extends NodeFamily[G] with PVLAccessImpl[G] { var ref: Option[RefField[G]] = None } case class PVLCommunicate[G](sender: PVLAccess[G], receiver: PVLAccess[G])(val blame: Blame[PVLCommunicateFailure])(implicit val o: Origin) extends Statement[G] with PVLCommunicateImpl[G] diff --git a/src/col/vct/col/ast/family/pvlcommunicate/PVLCommunicateAccessImpl.scala b/src/col/vct/col/ast/family/pvlcommunicate/PVLAccessImpl.scala similarity index 68% rename from src/col/vct/col/ast/family/pvlcommunicate/PVLCommunicateAccessImpl.scala rename to src/col/vct/col/ast/family/pvlcommunicate/PVLAccessImpl.scala index 7126a7642c..a03c168d4a 100644 --- a/src/col/vct/col/ast/family/pvlcommunicate/PVLCommunicateAccessImpl.scala +++ b/src/col/vct/col/ast/family/pvlcommunicate/PVLAccessImpl.scala @@ -2,6 +2,6 @@ package vct.col.ast.family.pvlcommunicate import vct.col.ast.{PVLAccess, Type} -trait PVLCommunicateAccessImpl[G] { this: PVLAccess[G] => +trait PVLAccessImpl[G] { this: PVLAccess[G] => def fieldType: Type[G] = ref.get.decl.t } diff --git a/src/col/vct/col/ast/family/pvlcommunicate/PVLCommunicateSubjectImpl.scala b/src/col/vct/col/ast/family/pvlcommunicate/PVLSubjectImpl.scala similarity index 78% rename from src/col/vct/col/ast/family/pvlcommunicate/PVLCommunicateSubjectImpl.scala rename to src/col/vct/col/ast/family/pvlcommunicate/PVLSubjectImpl.scala index b6aa7d0eb3..85785136cf 100644 --- a/src/col/vct/col/ast/family/pvlcommunicate/PVLCommunicateSubjectImpl.scala +++ b/src/col/vct/col/ast/family/pvlcommunicate/PVLSubjectImpl.scala @@ -3,7 +3,7 @@ package vct.col.ast.family.pvlcommunicate import vct.col.ast.{PVLSubject, TClass, Class, Type} import vct.col.resolve.ctx.RefPVLEndpoint -trait PVLCommunicateSubjectImpl[G] { this: PVLSubject[G] => +trait PVLSubjectImpl[G] { this: PVLSubject[G] => def cls: Class[G] = ref.get.decl.cls.decl def ref: Option[RefPVLEndpoint[G]] } From c903dfbc63e87e82fbb1e72393d1a2750e0dcf72 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 22 Nov 2023 11:35:08 +0100 Subject: [PATCH 21/32] Finalize Access/SeqAssign blames and bring tests up to date with permission generation --- src/col/vct/col/ast/Node.scala | 8 +- src/col/vct/col/origin/Blame.scala | 16 +- .../vct/col/typerules/CoercingRewriter.scala | 4 +- .../vct/parsers/transform/PVLToCol.scala | 6 +- .../vct/rewrite/lang/LangVeyMontToCol.scala | 4 +- .../vct/rewrite/veymont/EncodeSeqProg.scala | 33 +- .../examples/TechnicalVeyMontSpec.scala | 466 ++++++++++-------- .../test/integration/helper/VercorsSpec.scala | 1 + 8 files changed, 308 insertions(+), 230 deletions(-) diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index 1447fc9d50..665dd3e49e 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -1268,10 +1268,10 @@ case class PVLIndexedFamilyName[G](family: String, index: Expr[G])(implicit val case class PVLFamilyRange[G](family: String, binder: String, start: Expr[G], end: Expr[G])(implicit val o: Origin) extends PVLSubject[G] with PVLFamilyRangeImpl[G] { var ref: Option[RefPVLEndpoint[G]] = None } -case class PVLAccess[G](subject: PVLSubject[G], field: String)(implicit val o: Origin) extends NodeFamily[G] with PVLAccessImpl[G] { +case class PVLAccess[G](subject: PVLSubject[G], field: String)(val blame: Blame[PVLAccessFailure])(implicit val o: Origin) extends NodeFamily[G] with PVLAccessImpl[G] { var ref: Option[RefField[G]] = None } -case class PVLCommunicate[G](sender: PVLAccess[G], receiver: PVLAccess[G])(val blame: Blame[PVLCommunicateFailure])(implicit val o: Origin) extends Statement[G] with PVLCommunicateImpl[G] +case class PVLCommunicate[G](sender: PVLAccess[G], receiver: PVLAccess[G])(implicit val o: Origin) extends Statement[G] with PVLCommunicateImpl[G] final case class PVLSeqAssign[G](receiver: Ref[G, PVLEndpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(val blame: Blame[PVLSeqAssignFailure])(implicit val o: Origin) extends Statement[G] with PVLSeqAssignImpl[G] final class Endpoint[G](val cls: Ref[G, Class[G]], val constructor: Ref[G, Procedure[G]], val args: Seq[Expr[G]])(implicit val o: Origin) extends Declaration[G] with EndpointImpl[G] @@ -1279,8 +1279,8 @@ final class SeqProg[G](val contract: ApplicableContract[G], val args : Seq[Varia final case class SeqRun[G](body: Statement[G], contract: ApplicableContract[G])(val blame: Blame[CallableFailure])(implicit val o: Origin) extends NodeFamily[G] with SeqRunImpl[G] sealed trait Subject[G] extends NodeFamily[G] final case class EndpointName[G](ref: Ref[G, Endpoint[G]])(implicit val o: Origin) extends Subject[G] with EndpointNameImpl[G] -case class Access[G](subject: Subject[G], field: Ref[G, InstanceField[G]])(implicit val o: Origin) extends NodeFamily[G] with AccessImpl[G] -final case class Communicate[G](receiver: Access[G], sender: Access[G])(val blame: Blame[CommunicateFailure])(implicit val o: Origin) extends Statement[G] with CommunicateImpl[G] +case class Access[G](subject: Subject[G], field: Ref[G, InstanceField[G]])(val blame: Blame[AccessFailure])(implicit val o: Origin) extends NodeFamily[G] with AccessImpl[G] +final case class Communicate[G](receiver: Access[G], sender: Access[G])(implicit val o: Origin) extends Statement[G] with CommunicateImpl[G] final case class SeqAssign[G](receiver: Ref[G, Endpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(val blame: Blame[SeqAssignFailure])(implicit val o: Origin) extends Statement[G] with SeqAssignImpl[G] final case class EndpointUse[G](ref: Ref[G, Endpoint[G]])(implicit val o: Origin) extends Expr[G] with EndpointUseImpl[G] diff --git a/src/col/vct/col/origin/Blame.scala b/src/col/vct/col/origin/Blame.scala index 0edcb7aa9b..54f53f1891 100644 --- a/src/col/vct/col/origin/Blame.scala +++ b/src/col/vct/col/origin/Blame.scala @@ -347,18 +347,24 @@ case class LoopUnanimityNotMaintained(guard1: Node[_], guard2: Node[_]) extends override def inlineDesc: String = "The agreement of two conditions in this branch could not be maintained for an arbitrary loop iteration." } -sealed trait PVLCommunicateFailure extends VerificationFailure -sealed trait CommunicateFailure extends PVLCommunicateFailure +sealed trait PVLAccessFailure extends VerificationFailure +sealed trait AccessFailure extends PVLAccessFailure -case class CommunicateTargetPermission(node: Access[_]) extends CommunicateFailure with NodeVerificationFailure { - override def code: String = "communicateTargetPermission" - override def descInContext: String = "There may be insufficient permission to access this field." +case class AccessInsufficientPermission(node: Access[_]) extends AccessFailure with NodeVerificationFailure { + override def code: String = "accessPerm" + override def descInContext: String = "There may be insufficient permission to access this field on this endpoint." override def inlineDescWithSource(source: String): String = s"There may be insufficient permission to access `$source`." } sealed trait PVLSeqAssignFailure extends VerificationFailure sealed trait SeqAssignFailure extends PVLSeqAssignFailure +case class SeqAssignInsufficientPermission(node: SeqAssign[_]) extends SeqAssignFailure with NodeVerificationFailure { + override def code: String = "seqAssignPerm" + override def descInContext: String = "There may be insufficient permission to access this field on this endpoint." + override def inlineDescWithSource(source: String): String = s"There may be insufficient permission to access `$source`." +} + sealed trait DerefInsufficientPermission extends FrontendDerefError case class InsufficientPermission(node: HeapDeref[_]) extends DerefInsufficientPermission with NodeVerificationFailure { override def code: String = "perm" diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index 40bf86fe77..97c4c2de25 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -1683,9 +1683,9 @@ abstract class CoercingRewriter[Pre <: Generation]() extends AbstractRewriter[Pr case w @ WandPackage(expr, stat) => WandPackage(res(expr), stat)(w.blame) case VeyMontAssignExpression(t,a) => VeyMontAssignExpression(t,a) case CommunicateX(r,s,t,a) => CommunicateX(r,s,t,a) - case c @ PVLCommunicate(s, r) if r.fieldType == s.fieldType => PVLCommunicate(s, r)(c.blame) + case c @ PVLCommunicate(s, r) if r.fieldType == s.fieldType => PVLCommunicate(s, r) case comm@PVLCommunicate(s, r) => throw IncoercibleExplanation(comm, s"The receiver should have type ${s.fieldType}, but actually has type ${r.fieldType}.") - case c @ Communicate(r, s) if r.field.decl.t == s.field.decl.t => Communicate(r, s)(c.blame) + case c @ Communicate(r, s) if r.field.decl.t == s.field.decl.t => Communicate(r, s) case comm@Communicate(r, s) => throw IncoercibleExplanation(comm, s"The receiver should have type ${s.field.decl.t}, but actually has type ${r.field.decl.t}.") case a @ PVLSeqAssign(r, f, v) => try { PVLSeqAssign(r, f, coerce(v, f.decl.t))(a.blame) } catch { diff --git a/src/parsers/vct/parsers/transform/PVLToCol.scala b/src/parsers/vct/parsers/transform/PVLToCol.scala index 9afca05794..876a58911a 100644 --- a/src/parsers/vct/parsers/transform/PVLToCol.scala +++ b/src/parsers/vct/parsers/transform/PVLToCol.scala @@ -357,9 +357,9 @@ case class PVLToCol[G](override val baseOrigin: Origin, case PvlLabel(_, label, _) => Label(new LabelDecl()(origin(stat).sourceName(convert(label))), Block(Nil)) case PvlForStatement(inner, _) => convert(inner) case PvlCommunicateStatement(_, receiver, Direction0("<-"), sender, _) => - PVLCommunicate(convert(sender), convert(receiver))(blame(stat)) + PVLCommunicate(convert(sender), convert(receiver)) case PvlCommunicateStatement(_, sender, Direction1("->"), receiver, _) => - PVLCommunicate(convert(sender), convert(receiver))(blame(stat)) + PVLCommunicate(convert(sender), convert(receiver)) case PvlSeqAssign(endpoint, _, field, _, _, expr, _) => PVLSeqAssign( new UnresolvedRef[G, PVLEndpoint[G]](convert(endpoint)), @@ -389,7 +389,7 @@ case class PVLToCol[G](override val baseOrigin: Origin, } def convert(implicit acc: AccessContext): PVLAccess[G] = acc match { - case Access0(subject, _, field) => PVLAccess(convert(subject), convert(field)) + case Access0(subject, _, field) => PVLAccess(convert(subject), convert(field))(blame(acc)) } def convert(implicit subject: SubjectContext): PVLSubject[G] = subject match { diff --git a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala index 8076d6d706..d19afb9988 100644 --- a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala @@ -38,10 +38,10 @@ case class LangVeyMontToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) exten val currentProg: ScopedStack[PVLSeqProg[Pre]] = ScopedStack() def rewriteCommunicate(comm: PVLCommunicate[Pre]): Communicate[Post] = - Communicate(rewriteAccess(comm.receiver), rewriteAccess(comm.sender))(comm.blame)(comm.o) + Communicate(rewriteAccess(comm.receiver), rewriteAccess(comm.sender))(comm.o) def rewriteAccess(access: PVLAccess[Pre]): Access[Post] = - Access[Post](rewriteSubject(access.subject), rw.succ(access.ref.get.decl))(access.o) + Access[Post](rewriteSubject(access.subject), rw.succ(access.ref.get.decl))(access.blame)(access.o) def rewriteSubject(subject: PVLSubject[Pre]): Subject[Post] = subject match { case subject@PVLEndpointName(name) => EndpointName[Post](endpointSucc.ref(subject.ref.get.decl))(subject.o) diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala index 11e9b9bf3f..90490d022f 100644 --- a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala @@ -3,7 +3,7 @@ package vct.rewrite.veymont import com.typesafe.scalalogging.LazyLogging import hre.util.ScopedStack import vct.col.ast.{Access, Assign, Block, Class, Communicate, Declaration, Deref, Endpoint, EndpointName, EndpointUse, Eval, Expr, Local, LocalDecl, Node, Procedure, Scope, SeqAssign, SeqProg, SeqRun, Statement, Subject, TClass, TVoid, Variable} -import vct.col.origin.{AssignFailed, Blame, CallableFailure, CommunicateFailure, CommunicateTargetPermission, DiagnosticOrigin, InsufficientPermission, Origin, PanicBlame, VerificationFailure} +import vct.col.origin.{AccessFailure, AccessInsufficientPermission, AssignFailed, Blame, CallableFailure, DiagnosticOrigin, InsufficientPermission, Origin, PanicBlame, SeqAssignFailure, SeqAssignInsufficientPermission, VerificationFailure} import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} import vct.col.util.AstBuildHelpers._ import vct.col.util.SuccessionMap @@ -140,16 +140,19 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog } } - case class ForwardAssignFailed(access: Access[_], blame: Blame[CommunicateFailure]) extends Blame[AssignFailed] { - override def blame(error: AssignFailed): Unit = { - blame.blame(CommunicateTargetPermission(access)) + case class ForwardToAccessFailure(access: Access[_]) extends Blame[VerificationFailure] { + override def blame(error: VerificationFailure): Unit = error match { + case error: AssignFailed => + access.blame.blame(AccessInsufficientPermission(access)) + case error: InsufficientPermission => + access.blame.blame(AccessInsufficientPermission(access)) + case _ => ??? } } - case class ForwardInsufficientPermission(access: Access[_], blame: Blame[CommunicateFailure]) extends Blame[InsufficientPermission] { - override def blame(error: InsufficientPermission): Unit = { - blame.blame(CommunicateTargetPermission(access)) - } + case class ForwardToSeqAssignFailure(assign: SeqAssign[_]) extends Blame[AssignFailed] { + override def blame(error: AssignFailed): Unit = + assign.blame.blame(SeqAssignInsufficientPermission(assign)) } override def dispatch(stat: Statement[Pre]): Statement[Post] = stat match { @@ -159,20 +162,20 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog Deref[Post]( Local(endpointSucc((mode, endpoint)).ref), succ(field) - )(GeneratedPerm), + )(PanicBlame("Unused by Silver encoding")), dispatch(e) - )(GeneratedPerm) + )(ForwardToSeqAssignFailure(assign)) case comm @ Communicate(receiver, sender) => implicit val o = comm.o Assign[Post]( - rewriteAccess(receiver, PanicBlame("This one should not be used.")), - rewriteAccess(sender, comm.blame) - )(ForwardAssignFailed(receiver, comm.blame)) + rewriteAccess(receiver), + rewriteAccess(sender) + )(ForwardToAccessFailure(receiver)) case stat => rewriteDefault(stat) } - def rewriteAccess(access: Access[Pre], blame: Blame[CommunicateFailure]): Expr[Post] = - Deref[Post](rewriteSubject(access.subject), succ(access.field.decl))(ForwardInsufficientPermission(access, blame))(access.o) + def rewriteAccess(access: Access[Pre]): Expr[Post] = + Deref[Post](rewriteSubject(access.subject), succ(access.field.decl))(ForwardToAccessFailure(access))(access.o) def rewriteSubject(subject: Subject[Pre]): Expr[Post] = subject match { case EndpointName(Ref(endpoint)) => Local[Post](endpointSucc((mode, endpoint)).ref)(subject.o) diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index 1606c02832..0d0791bee5 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -7,36 +7,47 @@ import vct.options.types.Verbosity import vct.test.integration.helper.VercorsSpec class TechnicalVeyMontSpec extends VercorsSpec { - vercors should verify using silicon in "example using communicate" pvl - """ - class Storage { - int x; - } - seq_program Example() { - endpoint alice = Storage(); - endpoint bob = Storage(); - - seq_run { - communicate alice.x <- bob.x; - communicate bob.x -> alice.x; - assert alice.x == bob.x; - } - } - """ + (vercors + should verify + using silicon + flag "--veymont-generate-permissions" + in "example using communicate" + pvl + """ + class Storage { + int x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + + seq_run { + communicate alice.x <- bob.x; + communicate bob.x -> alice.x; + assert alice.x == bob.x; + } + } + """) - vercors should fail withCode "assertFailed:false" using silicon in "plain endpoint field dereference should be possible" pvl - """ - class Storage { - int x; - } - seq_program Example() { - endpoint alice = Storage(); + (vercors + should fail + withCode "assertFailed:false" + using silicon + flag "--veymont-generate-permissions" + in "plain endpoint field dereference should be possible" + pvl + """ + class Storage { + int x; + } + seq_program Example() { + endpoint alice = Storage(); - seq_run { - assert alice.x == 0; - } - } - """ + seq_run { + assert alice.x == 0; + } + } + """) vercors should error withCode "noSuchName" in "non-existent thread name in communicate fails" pvl """ @@ -82,17 +93,22 @@ class TechnicalVeyMontSpec extends VercorsSpec { } """ - vercors should verify using silicon in "Endpoint fields should be assignable" pvl - """ - class Storage { int x; int y; } - seq_program Example() { - endpoint alice = Storage(); + (vercors + should verify + using silicon + flag "--veymont-generate-permissions" + in "Endpoint fields should be assignable" + pvl + """ + class Storage { int x; int y; } + seq_program Example() { + endpoint alice = Storage(); - seq_run { - alice.x := alice.y; + seq_run { + alice.x := alice.y; + } } - } - """ + """) vercors should error withCode "resolutionError:seqProgInstanceMethodArgs" in "instance method in seq_program cannot have arguments" pvl """ @@ -187,51 +203,59 @@ class TechnicalVeyMontSpec extends VercorsSpec { } """ - /* TODO: In the new veymont, this test will probably be replaced by one that manually manages the - permissions for alice.x - */ - vercors should verify using silicon in "assignment should work" pvl - """ - class Storage { - int x; + (vercors + should verify + using silicon + flag "--veymont-generate-permissions" + in "assignment should work" + pvl + """ + class Storage { + int x; - ensures Perm(x, 1) ** x == 0; - constructor() { - x = 0; + ensures x == 0; + constructor() { + x = 0; + } } - } - seq_program Example() { - endpoint alice = Storage(); + seq_program Example() { + endpoint alice = Storage(); - requires alice.x == 0; - ensures alice.x == 0; - seq_run { - assert alice.x == 0; - } - } - """ + requires alice.x == 0; + ensures alice.x == 0; + seq_run { + assert alice.x == 0; + } + } + """) // TODO: Eventually should be postconditionFailed if the assignment statement works succesfully - vercors should error withCode "callableFailureNotSupported" in "assigning should change state" pvl - """ - class Storage { - int x; + (vercors + should fail + withCode "postconditionFailed?" + using silicon + flag "--veymont-generate-permissions" + in "assigning should change state" + pvl + """ + class Storage { + int x; - ensures Perm(x, write) ** x == v; - constructor(int v) { - x = v; - } - } - seq_program Example() { - endpoint alice = Storage(0); + ensures x == v; + constructor(int v) { + x = v; + } + } + seq_program Example() { + endpoint alice = Storage(0); - requires alice.x == 0; - ensures alice.x == 0; - seq_run { - alice.x := 1; - } - } - """ + requires alice.x == 0; + ensures alice.x == 0; + seq_run { + alice.x := 1; + } + } + """) vercors should error withCode "resolutionError:seqProgReceivingEndpoint" in "Assignment statement only allows one endpoint in the assigned expression" pvl """ @@ -248,61 +272,78 @@ class TechnicalVeyMontSpec extends VercorsSpec { } """ - vercors should fail withCode "branchNotUnanimous" using silicon in "Parts of condition in branch have to agree inside seqprog" pvl - """ - class Storage { - int x; - } - seq_program Example() { - endpoint alice = Storage(); - endpoint bob = Storage(); + (vercors + should fail + withCode "branchNotUnanimous" + using silicon + flag "--veymont-generate-permissions" + in "Parts of condition in branch have to agree inside seqprog" + pvl + """ + class Storage { + int x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); - seq_run { - if (alice.x == 0 && bob.x == 0) { - // Alice might go here, bob might not: error - } - } - } - """ + seq_run { + if (alice.x == 0 && bob.x == 0) { + // Alice might go here, bob might not: error + } + } + } + """) - vercors should fail withCode "branchNotUnanimous" using silicon in "Parts of condition in branch have to agree inside seqprog, including conditions for all endpoints" pvl - """ - class Storage { - int x; - } + (vercors + should fail + withCode "branchNotUnanimous" + using silicon + flag "--veymont-generate-permissions" + in "Parts of condition in branch have to agree inside seqprog, including conditions for all endpoints" + pvl + """ + class Storage { + int x; + } - pure int f() = 3; + pure int f() = 3; - seq_program Example() { - endpoint alice = Storage(); - endpoint bob = Storage(); + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); - seq_run { - if (alice.x == 0 && f() == 3) { - // Alice might go here, bob will definitely, because of the second expression: error - } - } - } - """ + seq_run { + if (alice.x == 0 && f() == 3) { + // Alice might go here, bob will definitely, because of the second expression: error + } + } + } + """) - vercors should verify using silicon in "If there is only one endpoint, the conditions don't have to agree, as there is only one endpoint" pvl - """ - class Storage { - int x; - } + (vercors + should verify + using silicon + flag "--veymont-generate-permissions" + in "If there is only one endpoint, the conditions don't have to agree, as there is only one endpoint" + pvl + """ + class Storage { + int x; + } - pure int f() = 3; + pure int f() = 3; - seq_program Example() { - endpoint alice = Storage(); + seq_program Example() { + endpoint alice = Storage(); - seq_run { - if (alice.x == 0 && f() == 3) { - // Alice might go here, bob will definitely, because of the second expression: error - } - } - } - """ + seq_run { + if (alice.x == 0 && f() == 3) { + // Alice might go here, bob will definitely, because of the second expression: error + } + } + } + """) vercors should error withCode "seqProgParticipantErrors" in "`if` cannot depend on bob, inside an `if` depending on alice" pvl """ @@ -357,61 +398,78 @@ class TechnicalVeyMontSpec extends VercorsSpec { } """ - vercors should verify using silicon in "Programs where branch conditions agree should verify" pvl - """ - class Storage { - bool x; - } - seq_program Example() { - endpoint alice = Storage(); - endpoint bob = Storage(); - - seq_run { - alice.x := true; - bob.x := true; - while (alice.x && bob.x) { - bob.x := false; - communicate alice.x <- bob.x; + (vercors + should verify + using silicon + flag "--veymont-generate-permissions" + in "Programs where branch conditions agree should verify" + pvl + """ + class Storage { + bool x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + + seq_run { + alice.x := true; + bob.x := true; + while (alice.x && bob.x) { + bob.x := false; + communicate alice.x <- bob.x; + } } - } - } - """ + } + """) - vercors should fail withCode "loopUnanimityNotEstablished" using silicon in "Programs where branch condition unanimity cannot be established should fail" pvl - """ - class Storage { - bool x; - } - seq_program Example() { - endpoint alice = Storage(); - endpoint bob = Storage(); + (vercors + should fail + withCode "loopUnanimityNotEstablished" + using silicon + flag "--veymont-generate-permissions" + in "Programs where branch condition unanimity cannot be established should fail" + pvl + """ + class Storage { + bool x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); - seq_run { - while (alice.x && bob.x) { + seq_run { + while (alice.x && bob.x) { + } } - } - } - """ - - vercors should fail withCode "loopUnanimityNotMaintained" using silicon in "Programs where branch condition unanimity cannot be maintained should fail" pvl - """ - class Storage { - bool x; - } - seq_program Example() { - endpoint alice = Storage(); - endpoint bob = Storage(); + } + """) - seq_run { - alice.x := true; - bob.x := true; - while (alice.x && bob.x) { - alice.x := false; + (vercors + should fail + withCode "loopUnanimityNotMaintained" + using silicon + flag "--veymont-generate-permissions" + in "Programs where branch condition unanimity cannot be maintained should fail" + pvl + """ + class Storage { + bool x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + + seq_run { + alice.x := true; + bob.x := true; + while (alice.x && bob.x) { + alice.x := false; + } } - } - } - """ + } + """) vercors should error withCode "seqProgParticipantErrors" in "Loops should also limit the number of participants" pvl """ @@ -434,28 +492,33 @@ class TechnicalVeyMontSpec extends VercorsSpec { } """ - vercors should verify using silicon in "Loops should also limit the number of participants when combined with branches" pvl - """ - class Storage { - bool x; - } - seq_program Example() { - endpoint alice = Storage(); - endpoint bob = Storage(); - endpoint charlie = Storage(); - - seq_run { - alice.x := true; - bob.x := true; - while (alice.x && bob.x) { - alice.x := false; - if (bob.x == true) { - bob.x := false; + (vercors + should verify + using silicon + flag "--veymont-generate-permissions" + in "Loops should also limit the number of participants when combined with branches" + pvl + """ + class Storage { + bool x; + } + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + endpoint charlie = Storage(); + + seq_run { + alice.x := true; + bob.x := true; + while (alice.x && bob.x) { + alice.x := false; + if (bob.x == true) { + bob.x := false; + } } } - } - } - """ + } + """) vercors should error withCode "seqProgParticipantErrors" in "Loops should also limit the number of participants when combined with branches" pvl """ @@ -483,12 +546,13 @@ class TechnicalVeyMontSpec extends VercorsSpec { (vercors should verify using silicon - flags Seq("--veymont-generate-permissions") + flag "--veymont-generate-permissions" in "Permission should be generated for constructors as well" pvl """ class Storage { int x; + ensures x == 2; int m() { x = 2; } @@ -502,12 +566,13 @@ class TechnicalVeyMontSpec extends VercorsSpec { } } """) -} -class TechnicalVeyMontSpec2 extends VercorsSpec { - (vercors should verify + (vercors + should fail + withCode "accessPerm" using silicon - in "Permission should be generated for constructors as well" pvl + in "When no permission is generated, a failure should occur on endpoint field access" + pvl """ class Storage { int x; @@ -515,15 +580,19 @@ class TechnicalVeyMontSpec2 extends VercorsSpec { seq_program Example() { endpoint alice = Storage(); + endpoint bob = Storage(); seq_run { - alice.x := 2; + communicate alice.x <- bob.x; } } """) - (vercors should verify + (vercors + should fail + withCode "seqAssignPerm" using silicon - in "Permission should be generated for constructors all" pvl + in "When no permission is generated, a failure should occur on seq assign field access" + pvl """ class Storage { int x; @@ -531,9 +600,8 @@ class TechnicalVeyMontSpec2 extends VercorsSpec { seq_program Example() { endpoint alice = Storage(); - endpoint bob = Storage(); seq_run { - communicate alice.x <- bob.x; + alice.x := 3; } } """) diff --git a/test/main/vct/test/integration/helper/VercorsSpec.scala b/test/main/vct/test/integration/helper/VercorsSpec.scala index e5cc270a87..99ff21efdd 100644 --- a/test/main/vct/test/integration/helper/VercorsSpec.scala +++ b/test/main/vct/test/integration/helper/VercorsSpec.scala @@ -199,6 +199,7 @@ abstract class VercorsSpec extends AnyFlatSpec { def in(desc: String): DescPhrase = new DescPhrase(verdict, backends, desc, _flags) def flags(args: Seq[String]): BackendPhrase = new BackendPhrase(verdict, reportPath, backends, args) + def flag(arg: String): BackendPhrase = new BackendPhrase(verdict, reportPath, backends, Seq(arg)) } class DescPhrase(val verdict: Verdict, val backends: Seq[Backend], val desc: String, val flags: Seq[String]) { From 0212a8862883957f041393d2ce6bcb5cf6839d26 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 22 Nov 2023 12:05:27 +0100 Subject: [PATCH 22/32] Add boilerplate for blames for SeqRun and SeqProg --- src/col/vct/col/ast/Node.scala | 8 +- src/col/vct/col/origin/Blame.scala | 6 ++ .../vct/rewrite/veymont/EncodeSeqProg.scala | 74 ++++++++++++------- .../veymont/ParalleliseVeyMontThreads.scala | 6 +- 4 files changed, 60 insertions(+), 34 deletions(-) diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index 665dd3e49e..39b200f4bf 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -1255,8 +1255,8 @@ final case class TEndpoint[G](cls: Ref[G, Endpoint[G]])(implicit val o: Origin = final class PVLEndpoint[G](val name: String, val cls: Ref[G, Class[G]], val args: Seq[Expr[G]])(implicit val o: Origin) extends ClassDeclaration[G] { var ref: Option[PVLConstructorTarget[G]] = None } -final class PVLSeqProg[G](val name: String, val declarations: Seq[ClassDeclaration[G]], val contract: ApplicableContract[G], val args: Seq[Variable[G]])(implicit val o: Origin) extends GlobalDeclaration[G] with PVLSeqProgImpl[G] with Declarator[G] -final case class PVLSeqRun[G](body: Statement[G], contract: ApplicableContract[G])(val blame: Blame[CallableFailure])(implicit val o: Origin) extends ClassDeclaration[G] +final class PVLSeqProg[G](val name: String, val declarations: Seq[ClassDeclaration[G]], val contract: ApplicableContract[G], val args: Seq[Variable[G]])(val blame: Blame[PVLSeqProgFailure])(implicit val o: Origin) extends GlobalDeclaration[G] with PVLSeqProgImpl[G] with Declarator[G] +final case class PVLSeqRun[G](body: Statement[G], contract: ApplicableContract[G])(val blame: Blame[PVLSeqRunFailure])(implicit val o: Origin) extends ClassDeclaration[G] sealed trait PVLSubject[G] extends NodeFamily[G] with PVLSubjectImpl[G] case class PVLEndpointName[G](name: String)(implicit val o: Origin) extends PVLSubject[G] with PVLEndpointNameImpl[G] { @@ -1275,8 +1275,8 @@ case class PVLCommunicate[G](sender: PVLAccess[G], receiver: PVLAccess[G])(impli final case class PVLSeqAssign[G](receiver: Ref[G, PVLEndpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(val blame: Blame[PVLSeqAssignFailure])(implicit val o: Origin) extends Statement[G] with PVLSeqAssignImpl[G] final class Endpoint[G](val cls: Ref[G, Class[G]], val constructor: Ref[G, Procedure[G]], val args: Seq[Expr[G]])(implicit val o: Origin) extends Declaration[G] with EndpointImpl[G] -final class SeqProg[G](val contract: ApplicableContract[G], val args : Seq[Variable[G]], val endpoints: Seq[Endpoint[G]], val run: SeqRun[G], val decls: Seq[ClassDeclaration[G]])(implicit val o: Origin) extends GlobalDeclaration[G] with SeqProgImpl[G] -final case class SeqRun[G](body: Statement[G], contract: ApplicableContract[G])(val blame: Blame[CallableFailure])(implicit val o: Origin) extends NodeFamily[G] with SeqRunImpl[G] +final class SeqProg[G](val contract: ApplicableContract[G], val args : Seq[Variable[G]], val endpoints: Seq[Endpoint[G]], val run: SeqRun[G], val decls: Seq[ClassDeclaration[G]])(val blame: Blame[SeqProgFailure])(implicit val o: Origin) extends GlobalDeclaration[G] with SeqProgImpl[G] +final case class SeqRun[G](body: Statement[G], contract: ApplicableContract[G])(val blame: Blame[SeqRunFailure])(implicit val o: Origin) extends NodeFamily[G] with SeqRunImpl[G] sealed trait Subject[G] extends NodeFamily[G] final case class EndpointName[G](ref: Ref[G, Endpoint[G]])(implicit val o: Origin) extends Subject[G] with EndpointNameImpl[G] case class Access[G](subject: Subject[G], field: Ref[G, InstanceField[G]])(val blame: Blame[AccessFailure])(implicit val o: Origin) extends NodeFamily[G] with AccessImpl[G] diff --git a/src/col/vct/col/origin/Blame.scala b/src/col/vct/col/origin/Blame.scala index 54f53f1891..cefafa85c6 100644 --- a/src/col/vct/col/origin/Blame.scala +++ b/src/col/vct/col/origin/Blame.scala @@ -365,6 +365,12 @@ case class SeqAssignInsufficientPermission(node: SeqAssign[_]) extends SeqAssign override def inlineDescWithSource(source: String): String = s"There may be insufficient permission to access `$source`." } +sealed trait PVLSeqProgFailure extends VerificationFailure +sealed trait SeqProgFailure extends PVLSeqProgFailure + +sealed trait PVLSeqRunFailure extends VerificationFailure +sealed trait SeqRunFailure extends PVLSeqRunFailure + sealed trait DerefInsufficientPermission extends FrontendDerefError case class InsufficientPermission(node: HeapDeref[_]) extends DerefInsufficientPermission with NodeVerificationFailure { override def code: String = "perm" diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala index 90490d022f..b6caffa7f6 100644 --- a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala @@ -3,12 +3,12 @@ package vct.rewrite.veymont import com.typesafe.scalalogging.LazyLogging import hre.util.ScopedStack import vct.col.ast.{Access, Assign, Block, Class, Communicate, Declaration, Deref, Endpoint, EndpointName, EndpointUse, Eval, Expr, Local, LocalDecl, Node, Procedure, Scope, SeqAssign, SeqProg, SeqRun, Statement, Subject, TClass, TVoid, Variable} -import vct.col.origin.{AccessFailure, AccessInsufficientPermission, AssignFailed, Blame, CallableFailure, DiagnosticOrigin, InsufficientPermission, Origin, PanicBlame, SeqAssignFailure, SeqAssignInsufficientPermission, VerificationFailure} +import vct.col.origin.{AccessFailure, AccessInsufficientPermission, AssignFailed, Blame, CallableFailure, ContextEverywhereFailedInPost, ContractedFailure, DiagnosticOrigin, ExceptionNotInSignals, InsufficientPermission, Origin, PanicBlame, PostconditionFailed, SeqAssignFailure, SeqAssignInsufficientPermission, SignalsFailed, TerminationMeasureFailed, VerificationFailure} import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} import vct.col.util.AstBuildHelpers._ import vct.col.util.SuccessionMap import vct.result.VerificationError.UserError -import EncodeSeqProg.{CallableFailureNotSupportedBlame, CommunicateNotSupported, GeneratedPerm, SeqAssignNotSupported} +import EncodeSeqProg.{CallableFailureToSeqProg, CallableFailureToSeqRun, CommunicateNotSupported, InsufficientPermissionToAccessFailure, AssignFailedToSeqAssignFailure, GeneratedPerm, SeqAssignNotSupported} import vct.col.ref.Ref import scala.collection.{mutable => mut} @@ -29,13 +29,48 @@ object EncodeSeqProg extends RewriterBuilder { object GeneratedPerm extends PanicBlame("Permissions for these locations should be generated.") - case class CallableFailureNotSupported(n: Node[_]) extends UserError { - override def code: String = "callableFailureNotSupported" - override def text: String = n.o.messageInContext("Failures of type CallableFailure are not yet supported for this node") +// case class CallableFailureNotSupported(n: Node[_]) extends UserError { +// override def code: String = "callableFailureNotSupported" +// override def text: String = n.o.messageInContext("Failures of type CallableFailure are not yet supported for this node") +// } + +// case class CallableFailureNotSupportedBlame(node: Node[_]) extends Blame[CallableFailure] { +// override def blame(error: CallableFailure): Unit = throw CallableFailureNotSupported(node) +// } + + case class CallableFailureToSeqRun(run: SeqRun[_]) extends Blame[CallableFailure] { + override def blame(error: CallableFailure): Unit = error match { + case PostconditionFailed(path, failure, node) => ??? + case TerminationMeasureFailed(applicable, apply, measure) => ??? + case ContextEverywhereFailedInPost(failure, node) => ??? + case SignalsFailed(failure, node) => ??? + case ExceptionNotInSignals(node) => ??? + } + } + + case class CallableFailureToSeqProg(prog: SeqProg[_]) extends Blame[CallableFailure] { + override def blame(error: CallableFailure): Unit = error match { + case PostconditionFailed(path, failure, node) => ??? + case TerminationMeasureFailed(applicable, apply, measure) => ??? + case ContextEverywhereFailedInPost(failure, node) => ??? + case SignalsFailed(failure, node) => ??? + case ExceptionNotInSignals(node) => ??? + } } - case class CallableFailureNotSupportedBlame(node: Node[_]) extends Blame[CallableFailure] { - override def blame(error: CallableFailure): Unit = throw CallableFailureNotSupported(node) + case class InsufficientPermissionToAccessFailure(access: Access[_]) extends Blame[VerificationFailure] { + override def blame(error: VerificationFailure): Unit = error match { + case _: AssignFailed => + access.blame.blame(AccessInsufficientPermission(access)) + case _: InsufficientPermission => + access.blame.blame(AccessInsufficientPermission(access)) + case _ => PanicBlame("Error should either be AssignFailed or InsufficientPermission").blame(error) + } + } + + case class AssignFailedToSeqAssignFailure(assign: SeqAssign[_]) extends Blame[AssignFailed] { + override def blame(error: AssignFailed): Unit = + assign.blame.blame(SeqAssignInsufficientPermission(assign)) } } @@ -111,7 +146,7 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog args = prog.args.map(arg => variableSucc((mode, arg))), contract = dispatch(prog.contract), body = Some(body) - )(PanicBlame("TODO: callable failure blame"))) + )(CallableFailureToSeqProg(prog))) } case _ => rewriteDefault(decl) @@ -136,25 +171,10 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog body = Some(dispatch(run.body)), outArgs = Seq(), typeArgs = Seq(), returnType = TVoid(), - )(CallableFailureNotSupportedBlame(run))) + )(CallableFailureToSeqRun(run))) } } - case class ForwardToAccessFailure(access: Access[_]) extends Blame[VerificationFailure] { - override def blame(error: VerificationFailure): Unit = error match { - case error: AssignFailed => - access.blame.blame(AccessInsufficientPermission(access)) - case error: InsufficientPermission => - access.blame.blame(AccessInsufficientPermission(access)) - case _ => ??? - } - } - - case class ForwardToSeqAssignFailure(assign: SeqAssign[_]) extends Blame[AssignFailed] { - override def blame(error: AssignFailed): Unit = - assign.blame.blame(SeqAssignInsufficientPermission(assign)) - } - override def dispatch(stat: Statement[Pre]): Statement[Post] = stat match { case assign@SeqAssign(Ref(endpoint), Ref(field), e) => implicit val o = assign.o @@ -164,18 +184,18 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog succ(field) )(PanicBlame("Unused by Silver encoding")), dispatch(e) - )(ForwardToSeqAssignFailure(assign)) + )(AssignFailedToSeqAssignFailure(assign)) case comm @ Communicate(receiver, sender) => implicit val o = comm.o Assign[Post]( rewriteAccess(receiver), rewriteAccess(sender) - )(ForwardToAccessFailure(receiver)) + )(InsufficientPermissionToAccessFailure(receiver)) case stat => rewriteDefault(stat) } def rewriteAccess(access: Access[Pre]): Expr[Post] = - Deref[Post](rewriteSubject(access.subject), succ(access.field.decl))(ForwardToAccessFailure(access))(access.o) + Deref[Post](rewriteSubject(access.subject), succ(access.field.decl))(InsufficientPermissionToAccessFailure(access))(access.o) def rewriteSubject(subject: Subject[Pre]): Expr[Post] = subject match { case EndpointName(Ref(endpoint)) => Local[Post](endpointSucc((mode, endpoint)).ref)(subject.o) diff --git a/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala b/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala index 0df0e59679..1a06e44e2f 100644 --- a/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala +++ b/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala @@ -2,8 +2,8 @@ package vct.rewrite.veymont import hre.util.ScopedStack import vct.col.ast.RewriteHelpers.{RewriteApplicableContract, RewriteClass, RewriteDeref, RewriteJavaClass, RewriteJavaConstructor, RewriteMethodInvocation} -import vct.col.ast.{AbstractRewriter, ApplicableContract, Assert, Assign, Block, BooleanValue, Branch, Class, ClassDeclaration, CommunicateX, Declaration, Deref, Endpoint, EndpointUse, Eval, Expr, InstanceField, InstanceMethod, JavaClass, JavaConstructor, JavaInvocation, JavaLocal, JavaMethod, JavaNamedType, JavaParam, JavaPublic, JavaTClass, Local, Loop, MethodInvocation, NewObject, Node, Procedure, Program, RunMethod, Scope, SeqProg, SeqRun, Statement, TClass, TVeyMontChannel, TVoid, ThisObject, ThisSeqProg, Type, UnitAccountedPredicate, Variable, VeyMontAssignExpression, SeqGuard} -import vct.col.origin.Origin +import vct.col.ast.{AbstractRewriter, ApplicableContract, Assert, Assign, Block, BooleanValue, Branch, Class, ClassDeclaration, CommunicateX, Declaration, Deref, Endpoint, EndpointUse, Eval, Expr, InstanceField, InstanceMethod, JavaClass, JavaConstructor, JavaInvocation, JavaLocal, JavaMethod, JavaNamedType, JavaParam, JavaPublic, JavaTClass, Local, Loop, MethodInvocation, NewObject, Node, Procedure, Program, RunMethod, Scope, SeqGuard, SeqProg, SeqRun, Statement, TClass, TVeyMontChannel, TVoid, ThisObject, ThisSeqProg, Type, UnitAccountedPredicate, Variable, VeyMontAssignExpression} +import vct.col.origin.{Origin, PanicBlame} import vct.col.resolve.ctx.RefJavaMethod import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder, RewriterBuilderArg, Rewritten} import vct.col.util.SuccessionMap @@ -284,7 +284,7 @@ case class ParalleliseEndpoints[Pre <: Generation](channelClass: JavaClass[_]) e TVoid[Post](), Seq.empty,Seq.empty,Seq.empty, Some(dispatch(run.body)), - dispatch(run.contract))(run.blame)(RunMethodOrigin(run)) + dispatch(run.contract))(PanicBlame("TODO: Convert InstanceMethod blame to SeqRun blame")/* run.blame */)(RunMethodOrigin(run)) } override def dispatch(node: Expr[Pre]): Expr[Post] = { From 292c8307a57b0b0f7b5523e95bc9a91f0d4928da Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 22 Nov 2023 13:59:05 +0100 Subject: [PATCH 23/32] Error reporting for seq_run, seq_program also looks good --- src/col/vct/col/ast/Node.scala | 8 +-- src/col/vct/col/origin/Blame.scala | 13 ++--- .../vct/rewrite/lang/LangVeyMontToCol.scala | 8 +-- .../vct/rewrite/veymont/EncodeSeqProg.scala | 49 ++++--------------- .../veymont/GenerateSeqProgPermissions.scala | 1 - .../examples/TechnicalVeyMontSpec.scala | 33 +++++++++---- 6 files changed, 42 insertions(+), 70 deletions(-) diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index 39b200f4bf..7bbdc64856 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -1255,8 +1255,8 @@ final case class TEndpoint[G](cls: Ref[G, Endpoint[G]])(implicit val o: Origin = final class PVLEndpoint[G](val name: String, val cls: Ref[G, Class[G]], val args: Seq[Expr[G]])(implicit val o: Origin) extends ClassDeclaration[G] { var ref: Option[PVLConstructorTarget[G]] = None } -final class PVLSeqProg[G](val name: String, val declarations: Seq[ClassDeclaration[G]], val contract: ApplicableContract[G], val args: Seq[Variable[G]])(val blame: Blame[PVLSeqProgFailure])(implicit val o: Origin) extends GlobalDeclaration[G] with PVLSeqProgImpl[G] with Declarator[G] -final case class PVLSeqRun[G](body: Statement[G], contract: ApplicableContract[G])(val blame: Blame[PVLSeqRunFailure])(implicit val o: Origin) extends ClassDeclaration[G] +final class PVLSeqProg[G](val name: String, val declarations: Seq[ClassDeclaration[G]], val contract: ApplicableContract[G], val args: Seq[Variable[G]])(val blame: Blame[SeqCallableFailure])(implicit val o: Origin) extends GlobalDeclaration[G] with PVLSeqProgImpl[G] with Declarator[G] +final case class PVLSeqRun[G](body: Statement[G], contract: ApplicableContract[G])(val blame: Blame[SeqCallableFailure])(implicit val o: Origin) extends ClassDeclaration[G] sealed trait PVLSubject[G] extends NodeFamily[G] with PVLSubjectImpl[G] case class PVLEndpointName[G](name: String)(implicit val o: Origin) extends PVLSubject[G] with PVLEndpointNameImpl[G] { @@ -1275,8 +1275,8 @@ case class PVLCommunicate[G](sender: PVLAccess[G], receiver: PVLAccess[G])(impli final case class PVLSeqAssign[G](receiver: Ref[G, PVLEndpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(val blame: Blame[PVLSeqAssignFailure])(implicit val o: Origin) extends Statement[G] with PVLSeqAssignImpl[G] final class Endpoint[G](val cls: Ref[G, Class[G]], val constructor: Ref[G, Procedure[G]], val args: Seq[Expr[G]])(implicit val o: Origin) extends Declaration[G] with EndpointImpl[G] -final class SeqProg[G](val contract: ApplicableContract[G], val args : Seq[Variable[G]], val endpoints: Seq[Endpoint[G]], val run: SeqRun[G], val decls: Seq[ClassDeclaration[G]])(val blame: Blame[SeqProgFailure])(implicit val o: Origin) extends GlobalDeclaration[G] with SeqProgImpl[G] -final case class SeqRun[G](body: Statement[G], contract: ApplicableContract[G])(val blame: Blame[SeqRunFailure])(implicit val o: Origin) extends NodeFamily[G] with SeqRunImpl[G] +final class SeqProg[G](val contract: ApplicableContract[G], val args : Seq[Variable[G]], val endpoints: Seq[Endpoint[G]], val run: SeqRun[G], val decls: Seq[ClassDeclaration[G]])(val blame: Blame[SeqCallableFailure])(implicit val o: Origin) extends GlobalDeclaration[G] with SeqProgImpl[G] +final case class SeqRun[G](body: Statement[G], contract: ApplicableContract[G])(val blame: Blame[SeqCallableFailure])(implicit val o: Origin) extends NodeFamily[G] with SeqRunImpl[G] sealed trait Subject[G] extends NodeFamily[G] final case class EndpointName[G](ref: Ref[G, Endpoint[G]])(implicit val o: Origin) extends Subject[G] with EndpointNameImpl[G] case class Access[G](subject: Subject[G], field: Ref[G, InstanceField[G]])(val blame: Blame[AccessFailure])(implicit val o: Origin) extends NodeFamily[G] with AccessImpl[G] diff --git a/src/col/vct/col/origin/Blame.scala b/src/col/vct/col/origin/Blame.scala index cefafa85c6..48f7468f08 100644 --- a/src/col/vct/col/origin/Blame.scala +++ b/src/col/vct/col/origin/Blame.scala @@ -217,12 +217,13 @@ case class SYCLItemMethodPreconditionFailed(node: InvokingNode[_]) extends NodeV sealed trait CallableFailure extends ConstructorFailure with JavaConstructorFailure sealed trait ContractedFailure extends CallableFailure -case class PostconditionFailed(path: Seq[AccountedDirection], failure: ContractFailure, node: ContractApplicable[_]) extends ContractedFailure with WithContractFailure { +sealed trait SeqCallableFailure extends CallableFailure +case class PostconditionFailed(path: Seq[AccountedDirection], failure: ContractFailure, node: ContractApplicable[_]) extends ContractedFailure with SeqCallableFailure with WithContractFailure { override def baseCode: String = "postFailed" override def descInContext: String = "Postcondition may not hold, since" override def inlineDescWithSource(node: String, failure: String): String = s"Postcondition of `$node` may not hold, since $failure." } -case class TerminationMeasureFailed(applicable: ContractApplicable[_], apply: Invocation[_], measure: DecreasesClause[_]) extends ContractedFailure with VerificationFailure { +case class TerminationMeasureFailed(applicable: ContractApplicable[_], apply: Invocation[_], measure: DecreasesClause[_]) extends ContractedFailure with SeqCallableFailure with VerificationFailure { override def code: String = "decreasesFailed" override def position: String = measure.o.shortPositionText override def desc: String = Message.messagesInContext( @@ -233,7 +234,7 @@ case class TerminationMeasureFailed(applicable: ContractApplicable[_], apply: In override def inlineDesc: String = s"${apply.o.inlineContextText} may not terminate, since `${measure.o.inlineContextText}` is not decreased or not bounded" } -case class ContextEverywhereFailedInPost(failure: ContractFailure, node: ContractApplicable[_]) extends ContractedFailure with WithContractFailure { +case class ContextEverywhereFailedInPost(failure: ContractFailure, node: ContractApplicable[_]) extends ContractedFailure with SeqCallableFailure with WithContractFailure { override def baseCode: String = "contextPostFailed" override def descInContext: String = "Context may not hold in postcondition, since" override def inlineDescWithSource(node: String, failure: String): String = s"Context of `$node` may not hold in the postcondition, since $failure." @@ -365,12 +366,6 @@ case class SeqAssignInsufficientPermission(node: SeqAssign[_]) extends SeqAssign override def inlineDescWithSource(source: String): String = s"There may be insufficient permission to access `$source`." } -sealed trait PVLSeqProgFailure extends VerificationFailure -sealed trait SeqProgFailure extends PVLSeqProgFailure - -sealed trait PVLSeqRunFailure extends VerificationFailure -sealed trait SeqRunFailure extends PVLSeqRunFailure - sealed trait DerefInsufficientPermission extends FrontendDerefError case class InsufficientPermission(node: HeapDeref[_]) extends DerefInsufficientPermission with NodeVerificationFailure { override def code: String = "perm" diff --git a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala index d19afb9988..19af4ada35 100644 --- a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala @@ -3,16 +3,12 @@ package vct.rewrite.lang import com.typesafe.scalalogging.LazyLogging import hre.util.ScopedStack import vct.col.ast._ -import vct.col.ast.RewriteHelpers._ -import vct.col.origin.{DiagnosticOrigin, Origin} -import vct.col.ref.Ref +import vct.col.origin.Origin import vct.col.resolve.ctx.RefPVLEndpoint import vct.col.rewrite.{Generation, Rewritten} -import vct.rewrite.lang.LangSpecificToCol import vct.col.util.SuccessionMap import vct.result.VerificationError.UserError -import vct.rewrite.lang.LangVeyMontToCol.{EndpointUseNotSupported, NoRunMethod} -import vct.rewrite.veymont.EncodeSeqProg.CommunicateNotSupported +import vct.rewrite.lang.LangVeyMontToCol.NoRunMethod case object LangVeyMontToCol { case object EndpointUseNotSupported extends UserError { diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala index b6caffa7f6..7a1c4970d0 100644 --- a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala @@ -3,12 +3,12 @@ package vct.rewrite.veymont import com.typesafe.scalalogging.LazyLogging import hre.util.ScopedStack import vct.col.ast.{Access, Assign, Block, Class, Communicate, Declaration, Deref, Endpoint, EndpointName, EndpointUse, Eval, Expr, Local, LocalDecl, Node, Procedure, Scope, SeqAssign, SeqProg, SeqRun, Statement, Subject, TClass, TVoid, Variable} -import vct.col.origin.{AccessFailure, AccessInsufficientPermission, AssignFailed, Blame, CallableFailure, ContextEverywhereFailedInPost, ContractedFailure, DiagnosticOrigin, ExceptionNotInSignals, InsufficientPermission, Origin, PanicBlame, PostconditionFailed, SeqAssignFailure, SeqAssignInsufficientPermission, SignalsFailed, TerminationMeasureFailed, VerificationFailure} +import vct.col.origin.{AccessFailure, AccessInsufficientPermission, AssignFailed, Blame, CallableFailure, ContextEverywhereFailedInPost, ContractedFailure, DiagnosticOrigin, ExceptionNotInSignals, InsufficientPermission, Origin, PanicBlame, PostconditionFailed, SeqAssignFailure, SeqAssignInsufficientPermission, SeqCallableFailure, SignalsFailed, TerminationMeasureFailed, VerificationFailure} import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} import vct.col.util.AstBuildHelpers._ import vct.col.util.SuccessionMap import vct.result.VerificationError.UserError -import EncodeSeqProg.{CallableFailureToSeqProg, CallableFailureToSeqRun, CommunicateNotSupported, InsufficientPermissionToAccessFailure, AssignFailedToSeqAssignFailure, GeneratedPerm, SeqAssignNotSupported} +import EncodeSeqProg.{AssignFailedToSeqAssignFailure, CallableFailureToSeqCallableFailure, InsufficientPermissionToAccessFailure} import vct.col.ref.Ref import scala.collection.{mutable => mut} @@ -17,44 +17,13 @@ object EncodeSeqProg extends RewriterBuilder { override def key: String = "encodeSeqProg" override def desc: String = "Encodes the semantics of a parallel VeyMont program." - case object CommunicateNotSupported extends UserError { - override def code: String = "communicateNotSupported" - override def text: String = "The `communicate` statement is not yet supported" - } - - case object SeqAssignNotSupported extends UserError { - override def code: String = "seqAssignNotSupported" - override def text: String = "The `:=` statement is not yet supported" - } - - object GeneratedPerm extends PanicBlame("Permissions for these locations should be generated.") - -// case class CallableFailureNotSupported(n: Node[_]) extends UserError { -// override def code: String = "callableFailureNotSupported" -// override def text: String = n.o.messageInContext("Failures of type CallableFailure are not yet supported for this node") -// } - -// case class CallableFailureNotSupportedBlame(node: Node[_]) extends Blame[CallableFailure] { -// override def blame(error: CallableFailure): Unit = throw CallableFailureNotSupported(node) -// } - - case class CallableFailureToSeqRun(run: SeqRun[_]) extends Blame[CallableFailure] { - override def blame(error: CallableFailure): Unit = error match { - case PostconditionFailed(path, failure, node) => ??? - case TerminationMeasureFailed(applicable, apply, measure) => ??? - case ContextEverywhereFailedInPost(failure, node) => ??? - case SignalsFailed(failure, node) => ??? - case ExceptionNotInSignals(node) => ??? - } - } + object SignalsAlwaysEmpty extends PanicBlame("signals always empty") - case class CallableFailureToSeqProg(prog: SeqProg[_]) extends Blame[CallableFailure] { + case class CallableFailureToSeqCallableFailure(seqBlame: Blame[SeqCallableFailure]) extends Blame[CallableFailure] { override def blame(error: CallableFailure): Unit = error match { - case PostconditionFailed(path, failure, node) => ??? - case TerminationMeasureFailed(applicable, apply, measure) => ??? - case ContextEverywhereFailedInPost(failure, node) => ??? - case SignalsFailed(failure, node) => ??? - case ExceptionNotInSignals(node) => ??? + case failure: SeqCallableFailure => seqBlame.blame(failure) + case SignalsFailed(failure, node) => SignalsAlwaysEmpty.blame(error) + case ExceptionNotInSignals(node) => SignalsAlwaysEmpty.blame(error) } } @@ -146,7 +115,7 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog args = prog.args.map(arg => variableSucc((mode, arg))), contract = dispatch(prog.contract), body = Some(body) - )(CallableFailureToSeqProg(prog))) + )(CallableFailureToSeqCallableFailure(prog.blame))) } case _ => rewriteDefault(decl) @@ -171,7 +140,7 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog body = Some(dispatch(run.body)), outArgs = Seq(), typeArgs = Seq(), returnType = TVoid(), - )(CallableFailureToSeqRun(run))) + )(CallableFailureToSeqCallableFailure(run.blame))) } } diff --git a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala index 198117449f..81e41738ff 100644 --- a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala +++ b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala @@ -17,7 +17,6 @@ object GenerateSeqProgPermissions extends RewriterBuilderArg[Boolean] { } case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = false) extends Rewriter[Pre] with LazyLogging { - println(s"Enabled: $enabled") val currentPerm: ScopedStack[Expr[Post]] = ScopedStack() diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index 0d0791bee5..fb1ca4b445 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -229,30 +229,43 @@ class TechnicalVeyMontSpec extends VercorsSpec { } """) - // TODO: Eventually should be postconditionFailed if the assignment statement works succesfully (vercors should fail - withCode "postconditionFailed?" + withCode "postFailed:false" using silicon flag "--veymont-generate-permissions" - in "assigning should change state" + in "Postcondition of seq_run can fail" pvl """ class Storage { int x; + } + seq_program Example() { + endpoint alice = Storage(); - ensures x == v; - constructor(int v) { - x = v; + ensures alice.x == 0; + seq_run { } } + """) + + (vercors + should fail + withCode "postFailed:false" + using silicon + flag "--veymont-generate-permissions" + in "Postcondition of seq_program can fail" + pvl + """ + class Storage { + int x; + } + + ensures 1 == 0; seq_program Example() { - endpoint alice = Storage(0); + endpoint alice = Storage(); - requires alice.x == 0; - ensures alice.x == 0; seq_run { - alice.x := 1; } } """) From 7440f1be4caa5a66ab7a4b2b7109eb2bc877799a Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 22 Nov 2023 14:48:08 +0100 Subject: [PATCH 24/32] oh no --- src/parsers/vct/parsers/transform/PVLToCol.scala | 10 +++++----- src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/parsers/vct/parsers/transform/PVLToCol.scala b/src/parsers/vct/parsers/transform/PVLToCol.scala index 876a58911a..f399b9fbaf 100644 --- a/src/parsers/vct/parsers/transform/PVLToCol.scala +++ b/src/parsers/vct/parsers/transform/PVLToCol.scala @@ -30,7 +30,7 @@ case class PVLToCol[G](override val baseOrigin: Origin, case ProgramDecl1(cls) => Seq(convert(cls)) case ProgramDecl2(enum) => Seq(convert(enum)) case ProgramDecl3(method) => Seq(convertProcedure(method)) - case ProgramDecl4(seqProg) => Seq(convertVeyMontProg(seqProg)) + case ProgramDecl4(seqProg) => Seq(convertSeqProg(seqProg)) } def convert(implicit enum: EnumDeclContext): Enum[G] = enum match { @@ -50,7 +50,7 @@ case class PVLToCol[G](override val baseOrigin: Origin, PVLSeqRun( convert(body), contract.consumeApplicableContract(blame(decl)) - )(blame(decl))) + )(blame(decl))(origin(decl).where(name = "run"))) case PvlEndpoint(_, name, _, ClassType0(endpointType, None), _, args, _, _) => new PVLEndpoint( convert(name), @@ -60,15 +60,15 @@ case class PVLToCol[G](override val baseOrigin: Origin, case PvlEndpoint(_, name, _, t@ClassType0(_, Some(_)), _, args, _, _) => ??(t) } - def convertVeyMontProg(implicit cls: DeclVeyMontSeqProgContext): PVLSeqProg[G] = cls match { + def convertSeqProg(implicit decl: DeclVeyMontSeqProgContext): PVLSeqProg[G] = decl match { case DeclVeyMontSeqProg0(contract, _, name, _, args, _, _, decls, _) => withContract(contract, contract => { new PVLSeqProg( convert(name), decls.map(convert(_)), - contract.consumeApplicableContract(blame(cls)), + contract.consumeApplicableContract(blame(decl)), args.map(convert(_)).getOrElse(Seq()) - )(origin(cls).sourceName(convert(name))) + )(blame(decl))(origin(decl).sourceName(convert(name))) }) } diff --git a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala index 19af4ada35..1953345b51 100644 --- a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala @@ -76,7 +76,7 @@ case class LangVeyMontToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) exten case decl => rw.dispatch(decl) } )._1 - )(prog.o) + )(prog.blame)(prog.o) ) } } From 46bfe6dc53fa4b53b9d9b360293935d362d03eba Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 22 Nov 2023 15:33:07 +0100 Subject: [PATCH 25/32] Add test for invariant generation --- src/col/vct/col/resolve/lang/Java.scala | 4 -- .../vct/rewrite/veymont/EncodeSeqProg.scala | 2 +- .../examples/TechnicalVeyMontSpec.scala | 66 +++++++++++++------ 3 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/col/vct/col/resolve/lang/Java.scala b/src/col/vct/col/resolve/lang/Java.scala index b94540385b..80bbf5243d 100644 --- a/src/col/vct/col/resolve/lang/Java.scala +++ b/src/col/vct/col/resolve/lang/Java.scala @@ -241,10 +241,6 @@ case object Java extends LazyLogging { case JavaClassPathEntry.Path(root) => Some(root) } - println(s"Resolution: $name") - println(s"maybeBasePath: $maybeBasePath") - println(s"classPath: ${ctx.javaClassPath}") - for { basePath <- maybeBasePath ns <- loader.load[G](basePath, name) diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala index 7a1c4970d0..b08433d60d 100644 --- a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala @@ -126,7 +126,7 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog currentRun.having(run) { for (endpoint <- currentProg.top.endpoints) { - endpointSucc((mode, endpoint)) = new Variable(TClass(succ[Class[Post]](endpoint.cls.decl))) + endpointSucc((mode, endpoint)) = new Variable(TClass(succ[Class[Post]](endpoint.cls.decl)))(endpoint.o) } for (arg <- currentProg.top.args) { diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index fb1ca4b445..1e75b7b520 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -50,7 +50,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """) vercors should error withCode "noSuchName" in "non-existent thread name in communicate fails" pvl - """ + """ seq_program Example() { seq_run { communicate charlie.x <- charlie.x; @@ -59,7 +59,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """ vercors should error withCode "noSuchName" in "non-existent field in communicate fails" pvl - """ + """ class Storage { int x; } seq_program Example() { endpoint charlie = Storage(); @@ -70,7 +70,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """ vercors should error withCode "parseError" in "parameterized sends not yet supported " pvl - """ + """ class Storage { int x; } seq_program Example() { endpoint alice[10] = Storage(); @@ -82,12 +82,12 @@ class TechnicalVeyMontSpec extends VercorsSpec { """ vercors should error withCode "noRunMethod" in "run method should always be present" pvl - """ + """ seq_program Example() { } """ vercors should error withCode "parseError" in "endpoints can only have class types" pvl - """ + """ seq_program Example() { endpoint alice = int(); } @@ -111,7 +111,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """) vercors should error withCode "resolutionError:seqProgInstanceMethodArgs" in "instance method in seq_program cannot have arguments" pvl - """ + """ seq_program Example() { void m(int x) { } @@ -120,7 +120,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """ vercors should error withCode "resolutionError:seqProgInstanceMethodBody" in "instance method in seq_program must have a body" pvl - """ + """ seq_program Example() { void m(); @@ -129,7 +129,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """ vercors should error withCode "resolutionError:seqProgInstanceMethodNonVoid" in "instance method in seq_program must have void return type" pvl - """ + """ seq_program Example() { int m() { } @@ -138,7 +138,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """ vercors should error withCode "resolutionError:seqProgStatement" in "seq_prog excludes certain statements" pvl - """ + """ class C { } seq_program Example(C c) { seq_run { @@ -148,7 +148,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """ vercors should error withCode "resolutionError:seqProgReceivingEndpoint" in "Dereferencing anything other than the receiving endpoint in the arguments of a endpoint method invocation is not supported yet" pvl - """ + """ class C { C d; void foo(int x); int x; } seq_program Example(C c) { endpoint c = C(); @@ -160,7 +160,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """ vercors should error withCode "resolutionError:seqProgInvocation" in "Only method calls on endpoints or seq_program are allowed within seq_program" pvl - """ + """ class C { C d; void foo(); } seq_program Example(C c) { endpoint c = C(); @@ -171,7 +171,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """ vercors should verify using silicon in "Empty seq_program must verify" pvl - """ + """ seq_program C() { seq_run { @@ -180,7 +180,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """ vercors should error withCode "resolutionError:type" in "Assign must be well-typed" pvl - """ + """ class C { int x; } seq_program C() { endpoint charlie = C(); @@ -191,7 +191,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """ vercors should error withCode "resolutionError:type" in "Communicating parties must agree on the type" pvl - """ + """ class C { int c; } class A { bool a; } seq_program C() { @@ -271,7 +271,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """) vercors should error withCode "resolutionError:seqProgReceivingEndpoint" in "Assignment statement only allows one endpoint in the assigned expression" pvl - """ + """ class Storage { int x; } @@ -359,7 +359,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """) vercors should error withCode "seqProgParticipantErrors" in "`if` cannot depend on bob, inside an `if` depending on alice" pvl - """ + """ class Storage { int x; } @@ -378,7 +378,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """ vercors should error withCode "seqProgParticipantErrors" in "If alice branches, bob cannot communicate" pvl - """ + """ class Storage { int x; } @@ -395,7 +395,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """ vercors should error withCode "seqProgParticipantErrors" in "If alice branches, bob cannot assign" pvl - """ + """ class Storage { int x; } @@ -485,7 +485,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """) vercors should error withCode "seqProgParticipantErrors" in "Loops should also limit the number of participants" pvl - """ + """ class Storage { bool x; } @@ -534,7 +534,7 @@ class TechnicalVeyMontSpec extends VercorsSpec { """) vercors should error withCode "seqProgParticipantErrors" in "Loops should also limit the number of participants when combined with branches" pvl - """ + """ class Storage { bool x; } @@ -619,3 +619,29 @@ class TechnicalVeyMontSpec extends VercorsSpec { } """) } + +class TechnicalVeyMontSpec2 extends VercorsSpec { + (vercors + should verify + using silicon + flag "--veymont-generate-permissions" + in "Permissions are generated for loop invariants" + pvl + """ + class Storage { + int x; + } + + seq_program Example(int N) { + endpoint alice = Storage(); + ensures alice.x == 10; + seq_run { + alice.x := 0; + loop_invariant 0 <= alice.x && alice.x <= 10; + while(alice.x < 10) { + alice.x := alice.x + 1; + } + } + } + """) +} From 91786cbc2578fad4f730a6a1cc7ad84c1e6491e2 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 22 Nov 2023 15:54:01 +0100 Subject: [PATCH 26/32] checkpoint before stripping out the var system --- .../veymont/EncodeSeqBranchUnanimity.scala | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala b/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala index a926a72973..8e186ba3a4 100644 --- a/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala @@ -1,5 +1,7 @@ package vct.rewrite.veymont +import hre.util.ScopedStack +import vct.col.ast.RewriteHelpers._ import vct.col.ast._ import vct.col.origin.{AssertFailed, Blame, BranchUnanimityFailed, LoopUnanimityNotEstablished, LoopUnanimityNotMaintained, Origin} import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} @@ -8,6 +10,8 @@ import vct.col.util.SuccessionMap import vct.result.VerificationError.UserError import vct.rewrite.veymont.EncodeSeqBranchUnanimity.{ForwardBranchUnanimity, ForwardLoopUnanimityNotEstablished, ForwardLoopUnanimityNotMaintained} +import scala.collection.mutable + object EncodeSeqBranchUnanimity extends RewriterBuilder { override def key: String = "encodeSeqBranchUnanimity" override def desc: String = "Encodes the branch unanimity requirement imposed by VeyMont on branches and loops in seq_program nodes." @@ -30,8 +34,12 @@ object EncodeSeqBranchUnanimity extends RewriterBuilder { case class EncodeSeqBranchUnanimity[Pre <: Generation]() extends Rewriter[Pre] { + val currentLoop = ScopedStack[SeqLoop[Pre]]() + val guardSucc = SuccessionMap[SeqGuard[Pre], Variable[Post]]() + val loopCondition = mutable.LinkedHashMap[SeqLoop[Pre], Variable[Post]]() + override def dispatch(statement: Statement[Pre]): Statement[Post] = statement match { case branch@SeqBranch(guards, yes, no) => implicit val o = statement.o @@ -100,12 +108,15 @@ case class EncodeSeqBranchUnanimity[Pre <: Generation]() extends Rewriter[Pre] { combinedCond.get, foldAnd(guards.map(guard => guardSucc(guard).get)) ) + loopCondition(loop) = combinedCond val finalLoop: Loop[Post] = Loop( Block(assignments ++ establishAssertions :+ combinedAssign), combinedCond.get, Block(assignments ++ maintainAssertions :+ combinedAssign), - dispatch(contract), + currentLoop.having(loop) { + dispatch(contract) + }, dispatch(body) ) @@ -113,4 +124,17 @@ case class EncodeSeqBranchUnanimity[Pre <: Generation]() extends Rewriter[Pre] { case statement => rewriteDefault(statement) } + +// def rewriteGuard(guard: SeqGuard[Pre]): Expr[Post] = dispatch(guard.condition) + + override def dispatch(contract: LoopContract[Pre]): LoopContract[Post] = (currentLoop.topOption, contract) match { + case (Some(loop), inv @ LoopInvariant(invariant, decreases)) => + implicit val o = contract.o + inv.rewrite( + invariant = + dispatch(inv.invariant) &* + (loopCondition(loop).get === foldAnd(loop.guards.map(guard => dispatch(guard.condition)))) + ) + case _ /* IterationContract(requires, ensures, context_everywhere) */ => rewriteDefault(contract) + } } From 2accc7db05b7c56c6591377b03dcb2c562219df2 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 22 Nov 2023 16:23:20 +0100 Subject: [PATCH 27/32] Fix bug in branch unanimity encoding (side effects are not real anyway) --- .../veymont/EncodeSeqBranchUnanimity.scala | 111 ++++++------------ 1 file changed, 35 insertions(+), 76 deletions(-) diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala b/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala index 8e186ba3a4..f80076c56a 100644 --- a/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeSeqBranchUnanimity.scala @@ -36,105 +36,64 @@ case class EncodeSeqBranchUnanimity[Pre <: Generation]() extends Rewriter[Pre] { val currentLoop = ScopedStack[SeqLoop[Pre]]() - val guardSucc = SuccessionMap[SeqGuard[Pre], Variable[Post]]() - - val loopCondition = mutable.LinkedHashMap[SeqLoop[Pre], Variable[Post]]() - override def dispatch(statement: Statement[Pre]): Statement[Post] = statement match { - case branch@SeqBranch(guards, yes, no) => + case branch @ SeqBranch(guards, yes, no) => implicit val o = statement.o - /* - for each c in conds: - bool bc = c; - - for each subsequent c1, c2 in conds: - assert c1 == c2 - - boolean cond = foldStar(succ(c)) - - if (cons) dispatch(yes) dispatch(no) - */ - val assignments: Seq[Assign[Post]] = guards.map { guard => - guardSucc(guard) = new Variable(TBool())(guard.o.where(name = "g")) - assignLocal(guardSucc(guard).get, dispatch(guard.condition)) - } - - val assertions: Seq[Assert[Post]] = (0 until guards.length - 1).map { i => - val c1 = guards(i) - val c2 = guards(i + 1) - Assert(guardSucc(c1).get === guardSucc(c2).get)(ForwardBranchUnanimity(branch, c1, c2)) - } - - val majorCond = new Variable[Post](TBool()) - val majorAssign: Assign[Post] = assignLocal[Post]( - majorCond.get, - foldAnd(guards.map(guard => guardSucc(guard).get)) - ) - val finalIf: Branch[Post] = Branch[Post](Seq( - (majorCond.get, dispatch(yes)), - (tt, no.map(dispatch).getOrElse(Block(Seq()))) - )) + val assertions: Block[Post] = Block(guards.indices.init.map { i => + Assert(rewriteGuard(guards(i)) === rewriteGuard(guards(i + 1)))( + ForwardBranchUnanimity(branch, guards(i), guards(i + 1))) + }) - Scope(guards.map(guardSucc(_)) :+ majorCond, Block( - assignments ++ - assertions :+ - majorAssign :+ - finalIf + Block(Seq( + assertions, + Branch[Post]( + Seq((foldAnd(guards.map(rewriteGuard)), dispatch(yes))) ++ + no.map { no => Seq((tt[Post], dispatch(no))) }.getOrElse(Nil)) )) - case loop@SeqLoop(guards, contract, body) => + case loop @ SeqLoop(guards, contract, body) => implicit val o = statement.o - val assignments: Seq[Assign[Post]] = guards.map { guard => - guardSucc(guard) = new Variable(TBool())(guard.o.where(name = "g")) - assignLocal(guardSucc(guard).get, dispatch(guard.condition)) - } - - val establishAssertions: Seq[Assert[Post]] = (0 until guards.length - 1).map { i => - val c1 = guards(i) - val c2 = guards(i + 1) - Assert(guardSucc(c1).get === guardSucc(c2).get)(ForwardLoopUnanimityNotEstablished(loop, c1, c2)) - } - - val maintainAssertions: Seq[Assert[Post]] = (0 until guards.length - 1).map { i => - val c1 = guards(i) - val c2 = guards(i + 1) - Assert(guardSucc(c1).get === guardSucc(c2).get)(ForwardLoopUnanimityNotMaintained(loop, c1, c2)) - } - - val combinedCond = new Variable[Post](TBool())(loop.o.where(name = "combined")) - val combinedAssign: Assign[Post] = assignLocal[Post]( - combinedCond.get, - foldAnd(guards.map(guard => guardSucc(guard).get)) - ) - loopCondition(loop) = combinedCond + val establishAssertions: Statement[Post] = Block(guards.indices.init.map { i => + Assert(rewriteGuard(guards(i)) === rewriteGuard(guards(i + 1)))( + ForwardLoopUnanimityNotEstablished(loop, guards(i), guards(i + 1))) + }) + + val maintainAssertions: Statement[Post] = Block(guards.indices.init.map { i => + Assert(rewriteGuard(guards(i)) === rewriteGuard(guards(i + 1)))( + ForwardLoopUnanimityNotMaintained(loop, guards(i), guards(i + 1))) + }) val finalLoop: Loop[Post] = Loop( - Block(assignments ++ establishAssertions :+ combinedAssign), - combinedCond.get, - Block(assignments ++ maintainAssertions :+ combinedAssign), + establishAssertions, + foldAnd(guards.map(rewriteGuard)), + maintainAssertions, currentLoop.having(loop) { dispatch(contract) }, dispatch(body) ) - Scope(guards.map(guardSucc(_)) :+ combinedCond, finalLoop) + finalLoop case statement => rewriteDefault(statement) } -// def rewriteGuard(guard: SeqGuard[Pre]): Expr[Post] = dispatch(guard.condition) + def rewriteGuard(guard: SeqGuard[Pre]): Expr[Post] = dispatch(guard.condition) + + def allEqual[G](exprs: Seq[Expr[G]])(implicit o: Origin): Expr[G] = + foldAnd[G](exprs.indices.init.map(i => exprs(i) === exprs(i + 1))) override def dispatch(contract: LoopContract[Pre]): LoopContract[Post] = (currentLoop.topOption, contract) match { - case (Some(loop), inv @ LoopInvariant(invariant, decreases)) => + case (Some(loop), inv: LoopInvariant[Pre]) => + implicit val o = contract.o + inv.rewrite(invariant = dispatch(inv.invariant) &* allEqual(loop.guards.map(rewriteGuard))) + case (Some(loop), inv @ IterationContract(requires, ensures, _)) => implicit val o = contract.o inv.rewrite( - invariant = - dispatch(inv.invariant) &* - (loopCondition(loop).get === foldAnd(loop.guards.map(guard => dispatch(guard.condition)))) - ) - case _ /* IterationContract(requires, ensures, context_everywhere) */ => rewriteDefault(contract) + requires = dispatch(requires) &* allEqual(loop.guards.map(rewriteGuard)), + ensures = dispatch(ensures) &* allEqual(loop.guards.map(rewriteGuard))) + case _ => rewriteDefault(contract) } } From b8fb7ec7300a56b70e54e6f70b6624c518bc689f Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 22 Nov 2023 16:30:01 +0100 Subject: [PATCH 28/32] Test for most permission generation facilities --- .../examples/TechnicalVeyMontSpec.scala | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index 1e75b7b520..0c2149b998 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -618,20 +618,42 @@ class TechnicalVeyMontSpec extends VercorsSpec { } } """) -} -class TechnicalVeyMontSpec2 extends VercorsSpec { (vercors should verify using silicon flag "--veymont-generate-permissions" - in "Permissions are generated for loop invariants" + in "Permissions are generated for loop invariants, procedures, functions, instance methods, instance functions" pvl """ class Storage { int x; + + ensures x == \old(x); + ensures \result == x; + int imx() { + return x; + } + + pure int ifx() = x; + + ensures x == \old(x); + void all() { + assert imx() == ifx(); + assert ifx() == px(this); + assert px(this) == fx(this); + } + } + + ensures s.x == \old(s.x); + ensures \result == s.x; + int px(Storage s) { + return s.x; } + ensures \result == s.x; + pure int fx(Storage s) = s.x; + seq_program Example(int N) { endpoint alice = Storage(); ensures alice.x == 10; @@ -641,6 +663,8 @@ class TechnicalVeyMontSpec2 extends VercorsSpec { while(alice.x < 10) { alice.x := alice.x + 1; } + + alice.all(); } } """) From 6ff61f8d9efbc2b655e1eb8e80a38476a1d45893 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Thu, 23 Nov 2023 11:11:32 +0100 Subject: [PATCH 29/32] Move GenerateSeqProgPermissions to be after branch unanimity encoding, such that all branch participants are know when permissions are generated --- src/main/vct/main/stages/Transformation.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index bf362a62b7..f24bdd6d45 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -195,11 +195,11 @@ case class SilverTransformation EncodeRangedFor, // VeyMont sequential program encoding - GenerateSeqProgPermissions.withArg(veymontGeneratePermissions), SplitSeqGuards, EncodeUnpointedGuard, DeduplicateSeqGuards, EncodeSeqBranchUnanimity, + GenerateSeqProgPermissions.withArg(veymontGeneratePermissions), EncodeSeqProg, EncodeString, // Encode spec string as seq From f333b65cacefa8b0a472de22c40118098c48a05c Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Thu, 23 Nov 2023 11:54:02 +0100 Subject: [PATCH 30/32] Make permission generation more strict --- src/main/vct/main/stages/Transformation.scala | 2 +- .../veymont/GenerateSeqProgPermissions.scala | 63 ++++++++++++++----- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index f24bdd6d45..23df74a5cc 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -198,8 +198,8 @@ case class SilverTransformation SplitSeqGuards, EncodeUnpointedGuard, DeduplicateSeqGuards, - EncodeSeqBranchUnanimity, GenerateSeqProgPermissions.withArg(veymontGeneratePermissions), + EncodeSeqBranchUnanimity, EncodeSeqProg, EncodeString, // Encode spec string as seq diff --git a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala index 81e41738ff..b1ffb30e96 100644 --- a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala +++ b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala @@ -4,12 +4,12 @@ import com.typesafe.scalalogging.LazyLogging import hre.util.ScopedStack import vct.col.ast.RewriteHelpers._ import vct.col.util.AstBuildHelpers._ -import vct.col.ast.{Applicable, ApplicableContract, ArraySubscript, BooleanValue, Class, ContractApplicable, Declaration, Deref, Endpoint, EndpointUse, EnumUse, Expr, FieldLocation, Function, InstanceField, InstanceFunction, InstanceMethod, IterationContract, Length, Local, LoopContract, LoopInvariant, Null, Perm, Procedure, Result, SeqProg, SeqRun, SplitAccountedPredicate, TArray, TClass, TInt, ThisObject, Type, UnitAccountedPredicate, Variable, WritePerm} +import vct.col.ast.{Applicable, ApplicableContract, ArraySubscript, BooleanValue, Class, ContractApplicable, Declaration, Deref, Endpoint, EndpointGuard, EndpointName, SeqLoop, EndpointUse, EnumUse, Expr, FieldLocation, Function, InstanceField, InstanceFunction, InstanceMethod, IterationContract, Length, Local, LoopContract, LoopInvariant, Node, Null, Perm, Procedure, Result, SeqAssign, SeqProg, SeqRun, SplitAccountedPredicate, Statement, TArray, TClass, TInt, ThisObject, Type, UnitAccountedPredicate, Variable, WritePerm} import vct.col.origin.{Origin, PanicBlame} import vct.col.ref.Ref -import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder, RewriterBuilderArg} +import vct.col.rewrite.{Generation, Rewriter, RewriterBuilderArg} -import scala.annotation.switch +import scala.collection.immutable.ListSet object GenerateSeqProgPermissions extends RewriterBuilderArg[Boolean] { override def key: String = "generateSeqProgPermissions" @@ -19,6 +19,7 @@ object GenerateSeqProgPermissions extends RewriterBuilderArg[Boolean] { case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = false) extends Rewriter[Pre] with LazyLogging { val currentPerm: ScopedStack[Expr[Post]] = ScopedStack() + val currentProg: ScopedStack[SeqProg[Pre]] = ScopedStack() /* - Permission generation table - Only considers nodes as necessary for VeyMont case studies. @@ -55,7 +56,18 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals implicit val o = fun.o classDeclarations.succeed(fun, fun.rewrite(contract = prependContract(fun.contract, currentPerm.top, tt))) + case method: InstanceMethod[Pre] if enabled && currentProg.nonEmpty => + implicit val o = method.o + classDeclarations.succeed(method, method.rewrite( + contract = prependContract( + method.contract, + endpointsPerm(participants(method).toSeq), + endpointsPerm(participants(method).toSeq) + ) + )) + case method: InstanceMethod[Pre] if enabled => + // Permission generation for InstanceMethods in classes implicit val o = method.o classDeclarations.succeed(method, method.rewrite( contract = prependContract( @@ -67,21 +79,23 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals case prog: SeqProg[Pre] if enabled => val run = prog.run - globalDeclarations.succeed(prog, prog.rewrite( - contract = prependContract( - prog.contract, - variablesPerm(prog.args)(prog.o), - variablesPerm(prog.args)(prog.o) - )(prog.o), - run = run.rewrite( + currentProg.having(prog) { + globalDeclarations.succeed(prog, prog.rewrite( contract = prependContract( - run.contract, - endpointsPerm(prog.endpoints)(run.o), - endpointsPerm(prog.endpoints)(run.o), - )(run.o), - body = currentPerm.having(endpointsPerm(prog.endpoints)(run.o)) { - rewriteDefault(run.body) - }))) + prog.contract, + variablesPerm(prog.args)(prog.o), + variablesPerm(prog.args)(prog.o) + )(prog.o), + run = run.rewrite( + contract = prependContract( + run.contract, + endpointsPerm(prog.endpoints)(run.o), + endpointsPerm(prog.endpoints)(run.o), + )(run.o), + body = currentPerm.having(endpointsPerm(prog.endpoints)(run.o)) { + rewriteDefault(run.body) + }))) + } case decl => rewriteDefault(decl) } @@ -98,6 +112,14 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals } ) + override def dispatch(statement: Statement[Pre]): Statement[Post] = statement match { + case loop: SeqLoop[Pre] => + currentPerm.having(endpointsPerm(participants(statement).toSeq)(loop.contract.o)) { + loop.rewriteDefault() + } + case statement => rewriteDefault(statement) + } + override def dispatch(loopContract: LoopContract[Pre]): LoopContract[Post] = (currentPerm.topOption, loopContract) match { case (Some(perm), invariant: LoopInvariant[pre]) => @@ -165,4 +187,11 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals fieldPerm[Post](`this`, succ(f), WritePerm()) &* transitivePerm(Deref[Post](`this`, succ(f))(PanicBlame("Permission for this field is already established")), f.t) } + + def participants(node: Node[Pre]): ListSet[Endpoint[Pre]] = + ListSet.from(node.collect { + case EndpointGuard(Ref(endpoint), _) => endpoint + case SeqAssign(Ref(endpoint), _, _) => endpoint + case EndpointName(Ref(endpoint)) => endpoint + }) } From 155300b9162fa8938718d8e4b8aac9ab8cbd18e5 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Thu, 23 Nov 2023 12:01:07 +0100 Subject: [PATCH 31/32] Add test for stricter permission generation --- .../examples/TechnicalVeyMontSpec.scala | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index 0c2149b998..3d9ca97d19 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -668,4 +668,30 @@ class TechnicalVeyMontSpec extends VercorsSpec { } } """) + + (vercors + should verify + using silicon + flag "--veymont-generate-permissions" + in "Permission generation should only generate permissions that are strictly necessary" + pvl + """ + class Storage { + int x; + } + + seq_program Example() { + endpoint alice = Storage(); + endpoint bob = Storage(); + seq_run { + alice.x := 0; + bob.x := 3; + loop_invariant 0 <= alice.x && alice.x <= 10; + while(alice.x < 10) { + alice.x := alice.x + 1; + } + assert bob.x == 3; + } + } + """) } From f80844feb95abb2c3240d288b7ed5b9a9e15143a Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Thu, 23 Nov 2023 13:43:57 +0100 Subject: [PATCH 32/32] Add support for auxiliary seq_prog methods, and tests --- .../ast/declaration/global/SeqProgImpl.scala | 14 ++- .../vct/rewrite/veymont/EncodeSeqProg.scala | 71 +++++++++++---- .../veymont/GenerateSeqProgPermissions.scala | 9 +- .../examples/TechnicalVeyMontSpec.scala | 86 +++++++++++++++++++ 4 files changed, 154 insertions(+), 26 deletions(-) diff --git a/src/col/vct/col/ast/declaration/global/SeqProgImpl.scala b/src/col/vct/col/ast/declaration/global/SeqProgImpl.scala index 2f3d423cd7..d5df0c2728 100644 --- a/src/col/vct/col/ast/declaration/global/SeqProgImpl.scala +++ b/src/col/vct/col/ast/declaration/global/SeqProgImpl.scala @@ -1,10 +1,22 @@ package vct.col.ast.declaration.global -import vct.col.ast.{Class, Declaration, SeqProg} +import vct.col.ast.{Class, Declaration, Endpoint, EndpointGuard, EndpointName, Node, SeqAssign, SeqProg} import vct.col.ast.util.Declarator import vct.col.check.{CheckContext, CheckError} import vct.col.origin.Origin import vct.col.print._ +import vct.col.ref.Ref + +import scala.collection.immutable.ListSet + +object SeqProgImpl { + def participants[G](node: Node[G]): ListSet[Endpoint[G]] = + ListSet.from(node.collect { + case EndpointGuard(Ref(endpoint), _) => endpoint + case SeqAssign(Ref(endpoint), _, _) => endpoint + case EndpointName(Ref(endpoint)) => endpoint + }) +} trait SeqProgImpl[G] extends Declarator[G] { this: SeqProg[G] => override def declarations: Seq[Declaration[G]] = args ++ endpoints ++ decls diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala index b08433d60d..9dc3bb4cd9 100644 --- a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala @@ -2,12 +2,12 @@ package vct.rewrite.veymont import com.typesafe.scalalogging.LazyLogging import hre.util.ScopedStack -import vct.col.ast.{Access, Assign, Block, Class, Communicate, Declaration, Deref, Endpoint, EndpointName, EndpointUse, Eval, Expr, Local, LocalDecl, Node, Procedure, Scope, SeqAssign, SeqProg, SeqRun, Statement, Subject, TClass, TVoid, Variable} +import vct.col.ast.{Access, Assign, Block, Class, Communicate, Declaration, Deref, Endpoint, EndpointName, EndpointUse, Eval, Expr, InstanceMethod, Local, LocalDecl, MethodInvocation, Node, Procedure, Scope, SeqAssign, SeqProg, SeqRun, Statement, Subject, TClass, TVoid, ThisSeqProg, Variable} import vct.col.origin.{AccessFailure, AccessInsufficientPermission, AssignFailed, Blame, CallableFailure, ContextEverywhereFailedInPost, ContractedFailure, DiagnosticOrigin, ExceptionNotInSignals, InsufficientPermission, Origin, PanicBlame, PostconditionFailed, SeqAssignFailure, SeqAssignInsufficientPermission, SeqCallableFailure, SignalsFailed, TerminationMeasureFailed, VerificationFailure} import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder} import vct.col.util.AstBuildHelpers._ import vct.col.util.SuccessionMap -import vct.result.VerificationError.UserError +import vct.result.VerificationError.{Unreachable, UserError} import EncodeSeqProg.{AssignFailedToSeqAssignFailure, CallableFailureToSeqCallableFailure, InsufficientPermissionToAccessFailure} import vct.col.ref.Ref @@ -47,34 +47,42 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog val currentProg: ScopedStack[SeqProg[Pre]] = ScopedStack() val currentRun: ScopedStack[SeqRun[Pre]] = ScopedStack() + val currentInstanceMethod: ScopedStack[InstanceMethod[Pre]] = ScopedStack() sealed trait Mode case object Top extends Mode case class InProg(prog: SeqProg[Pre]) extends Mode case class InRun(prog: SeqProg[Pre], run: SeqRun[Pre]) extends Mode - - def mode: Mode = (currentProg.topOption, currentRun.topOption) match { - case (None, None) => Top - case (Some(prog), None) => InProg(prog) - case (Some(prog), Some(run)) => InRun(prog, run) - case (None, Some(_)) => throw new RuntimeException() + case class InMethod(prog: SeqProg[Pre], method: InstanceMethod[Pre]) extends Mode + + def mode: Mode = (currentProg.topOption, currentRun.topOption, currentInstanceMethod.topOption) match { + case (None, None, None) => Top + case (Some(prog), None, None) => InProg(prog) + case (Some(prog), Some(run), None) => InRun(prog, run) + case (Some(prog), None, Some(method)) => InMethod(prog, method) + case (None, None, Some(_)) => Top + case (_, _, _) => throw Unreachable("AST structure should prevent this case") } val runSucc: mut.Map[SeqRun[Pre], Procedure[Post]] = mut.LinkedHashMap() val progSucc: SuccessionMap[SeqProg[Pre], Procedure[Post]] = SuccessionMap() + val methodSucc: SuccessionMap[InstanceMethod[Pre], Procedure[Post]] = SuccessionMap() val endpointSucc: SuccessionMap[(Mode, Endpoint[Pre]), Variable[Post]] = SuccessionMap() val variableSucc: SuccessionMap[(Mode, Variable[Pre]), Variable[Post]] = SuccessionMap() - override def dispatch(decl: Declaration[Pre]): Unit = decl match { - case prog: SeqProg[Pre] => currentProg.having(prog) { + override def dispatch(decl: Declaration[Pre]): Unit = (mode, decl) match { + case (Top, prog: SeqProg[Pre]) => currentProg.having(prog) { // First generate a procedure that implements the run method - rewriteRun(prog.run) + rewriteRun(prog) + + // And also process all auxiliary instance methods + prog.decls.foreach(dispatch) // Then generate a procedure that initializes all the endpoints and calls the run procedure // First set up the succesor variables that will be encoding the seq_program argument and endpoints implicit val o = prog.o prog.endpoints.foreach(_.drop()) - for (endpoint <- currentProg.top.endpoints) { + for (endpoint <- prog.endpoints) { endpointSucc((mode, endpoint)) = new Variable(TClass(succ[Class[Post]](endpoint.cls.decl)))(endpoint.o) } @@ -118,24 +126,43 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog )(CallableFailureToSeqCallableFailure(prog.blame))) } + case (InProg(prog), method: InstanceMethod[Pre]) => currentInstanceMethod.having(method) { + for (endpoint <- prog.endpoints) { + endpointSucc((mode, endpoint)) = new Variable(TClass(succ[Class[Post]](endpoint.cls.decl)))(endpoint.o) + } + + prog.args.foreach(_.drop()) + for (arg <- prog.args) { + variableSucc((mode, arg)) = new Variable(dispatch(arg.t))(arg.o) + } + + methodSucc(method) = globalDeclarations.declare(new Procedure( + args = prog.args.map(arg => variableSucc((mode, arg))) ++ + prog.endpoints.map(endpoint => endpointSucc((mode, endpoint))), + body = method.body.map(dispatch), + outArgs = Nil, typeArgs = Nil, returnType = dispatch(method.returnType), contract = dispatch(method.contract) + )(method.blame)(method.o)) + } + case _ => rewriteDefault(decl) } - def rewriteRun(run: SeqRun[Pre]): Unit = { + def rewriteRun(prog: SeqProg[Pre]): Unit = { + val run = prog.run implicit val o: Origin = run.o.where(name = currentProg.top.o.getPreferredNameOrElse().snake + "_run") currentRun.having(run) { - for (endpoint <- currentProg.top.endpoints) { + for (endpoint <- prog.endpoints) { endpointSucc((mode, endpoint)) = new Variable(TClass(succ[Class[Post]](endpoint.cls.decl)))(endpoint.o) } - for (arg <- currentProg.top.args) { + for (arg <- prog.args) { variableSucc((mode, arg)) = new Variable(dispatch(arg.t))(arg.o) } runSucc(run) = globalDeclarations.declare(new Procedure( - args = currentProg.top.args.map(arg => variableSucc((mode, arg))) ++ - currentProg.top.endpoints.map(endpoint => endpointSucc((mode, endpoint))), + args = prog.args.map(arg => variableSucc((mode, arg))) ++ + prog.endpoints.map(endpoint => endpointSucc((mode, endpoint))), contract = dispatch(run.contract), body = Some(dispatch(run.body)), outArgs = Seq(), typeArgs = Seq(), @@ -175,6 +202,16 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog Local[Post](endpointSucc((mode, endpoint)).ref)(expr.o) case (mode, Local(Ref(v))) if mode != Top && currentProg.top.args.contains(v) => Local[Post](variableSucc((mode, v)).ref)(expr.o) + case (mode, invocation @ MethodInvocation(ThisSeqProg(_), Ref(method), args, _, _, _, _)) if mode != Top => + implicit val o = invocation.o + val prog = currentProg.top + assert(args.isEmpty) + procedureInvocation( + ref = methodSucc.ref(method), + args = prog.args.map(arg => Local[Post](variableSucc.ref((mode, arg)))(arg.o)) ++ + prog.endpoints.map(endpoint => Local[Post](endpointSucc.ref((mode, endpoint)))(invocation.o)), + blame = invocation.blame + ) case (_, expr) => rewriteDefault(expr) } } diff --git a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala index b1ffb30e96..b67955eb59 100644 --- a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala +++ b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala @@ -5,10 +5,10 @@ import hre.util.ScopedStack import vct.col.ast.RewriteHelpers._ import vct.col.util.AstBuildHelpers._ import vct.col.ast.{Applicable, ApplicableContract, ArraySubscript, BooleanValue, Class, ContractApplicable, Declaration, Deref, Endpoint, EndpointGuard, EndpointName, SeqLoop, EndpointUse, EnumUse, Expr, FieldLocation, Function, InstanceField, InstanceFunction, InstanceMethod, IterationContract, Length, Local, LoopContract, LoopInvariant, Node, Null, Perm, Procedure, Result, SeqAssign, SeqProg, SeqRun, SplitAccountedPredicate, Statement, TArray, TClass, TInt, ThisObject, Type, UnitAccountedPredicate, Variable, WritePerm} +import vct.col.ast.declaration.global.SeqProgImpl.participants import vct.col.origin.{Origin, PanicBlame} import vct.col.ref.Ref import vct.col.rewrite.{Generation, Rewriter, RewriterBuilderArg} - import scala.collection.immutable.ListSet object GenerateSeqProgPermissions extends RewriterBuilderArg[Boolean] { @@ -187,11 +187,4 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals fieldPerm[Post](`this`, succ(f), WritePerm()) &* transitivePerm(Deref[Post](`this`, succ(f))(PanicBlame("Permission for this field is already established")), f.t) } - - def participants(node: Node[Pre]): ListSet[Endpoint[Pre]] = - ListSet.from(node.collect { - case EndpointGuard(Ref(endpoint), _) => endpoint - case SeqAssign(Ref(endpoint), _, _) => endpoint - case EndpointName(Ref(endpoint)) => endpoint - }) } diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index 3d9ca97d19..addb10d690 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -694,4 +694,90 @@ class TechnicalVeyMontSpec extends VercorsSpec { } } """) + + (vercors + should verify + using silicon + flag "--veymont-generate-permissions" + in "Calling auxiliary methods in seq_prog should be possible" + pvl + """ + class Storage { + int x; + } + + seq_program Example(int N) { + endpoint alice = Storage(); + + ensures alice.x == \old(alice.x) + 1; + void step() { + alice.x := alice.x + 1; + } + + ensures alice.x == \old(alice.x + 1); + seq_run { + step(); + } + } + """) + + (vercors + should verify + using silicon + flag "--veymont-generate-permissions" + in "VeyMont should conservatively generate permissions for auxiliary methods" + pvl + """ + class Storage { + int x; + } + + seq_program Example(int N) { + endpoint alice = Storage(); + endpoint bob = Storage(); + + ensures alice.x == \old(alice.x) + 1; + void step() { + alice.x := alice.x + 1; + } + + ensures alice.x == \old(alice.x + 1); + seq_run { + bob.x := 3; + step(); + assert bob.x == 3; + } + } + """) + + (vercors + should fail + withCode "assertFailed:false" + using silicon + flag "--veymont-generate-permissions" + in "Permissions should be generated when an endpoint participates in an auxiliary method" + pvl + """ + class Storage { + int x; + } + + seq_program Example(int N) { + endpoint alice = Storage(); + endpoint bob = Storage(); + + ensures alice.x == \old(alice.x) + 1; + void step() { + bob.x := 0; + alice.x := alice.x + 1; + } + + ensures alice.x == \old(alice.x + 1); + seq_run { + bob.x := 3; + step(); + assert bob.x == 3; + } + } + """) }