Skip to content

Commit

Permalink
Merge pull request #1259 from utwente-fmt/veymont-lightweight-heavywe…
Browse files Browse the repository at this point in the history
…ight

VeyMont: support inline, wrapped, and no stratified permissions, fix some of Wander's issues, update VeyMont pass order.
  • Loading branch information
bobismijnnaam authored Oct 18, 2024
2 parents 9c739cc + 525e1ce commit ab31613
Show file tree
Hide file tree
Showing 32 changed files with 876 additions and 245 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ choreography Main() {
ensures (\endpoint p2; state(p2, false));
requires (\endpoint p1; p1.turn ==> turnInvariant(p1, p2));
requires (\endpoint p2; p2.turn ==> turnInvariant(p2, p1));
requires (\chor p1.turn != p2.turn);
// Turned off as it is only required for branch unanimity, which is off for this test
requires (\chor (\endpoint p1; p1.turn) != (\endpoint p2; p2.turn));
requires p1.emptyBoard() ** p2.emptyBoard();
ensures (\endpoint p1;
([3\4]p1.boardPerm()) **
Expand All @@ -29,12 +30,15 @@ choreography Main() {
([3\4]p2.boardPerm()) **
([1\4]p1.boardPerm()) **
p2.equalBoard(p1));
ensures p1.gameFinished() && p2.gameFinished();
// This can be concluded informally by: 1) one of the boards is finished, 2) the boards are both equal
// Maybe someday if we properly integrate deadlock freedom checking with stratified permissions, this can be turned back on
// ensures p1.gameFinished() && p2.gameFinished();
run {
loop_invariant (\endpoint p1; state(p1, true)) ** (\endpoint p2; state(p2, true));
loop_invariant (\endpoint p1; p1.turn ==> turnInvariant(p1, p2));
loop_invariant (\endpoint p2; p2.turn ==> turnInvariant(p2, p1));
loop_invariant (\chor p1.turn != p2.turn);
// Turned off as it is only required for branch unanimity, which is off for this test
loop_invariant (\chor (\endpoint p1; p1.turn) != (\endpoint p2; p2.turn));
while (!p1.gameFinished() && !p2.gameFinished()) {
if (p1.turn && !p2.turn) {
p1.createNewMove();
Expand All @@ -45,7 +49,6 @@ choreography Main() {
\sender.oneMoveAheadOf(\msg, \receiver);
communicate p2.move <- p1.move.copy();
p2.doMove();
assert (\chor p2.equalBoard(p1));
} else {
p2.createNewMove();
p2.doMove();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,22 @@ choreography Main() {
turnInvariant(p1, p2) ** ([1\2]p1.returnerBound(p2)) ** p1.returnerMark());
requires (\endpoint p2; p2.turn ==>
turnInvariant(p1, p2) ** ([1\2]p2.returnerBound(p1)) ** p2.returnerMark());
requires (\chor p1.turn != p2.turn);
// Turned off as it is only required for branch unanimity, which is off for this test
// requires (\chor p1.turn != p2.turn);
ensures (\endpoint p1; state(p1, p2, true)) ** (\endpoint p2; state(p2, p1, true));
ensures p1.gameFinished() && p2.gameFinished();
// This can be concluded informally by: 1) one of the boards is finished, 2) the boards are both equal
// Maybe someday if we properly integrate deadlock freedom checking with stratified permissions, this can be turned back on
// ensures p1.gameFinished() && p2.gameFinished();
ensures (\endpoint p1; ([1\2]p1.returnerMark()) **
(p1.returner.mark == p1.myMark ==> turnInvariant(p1, p2)));
ensures (\endpoint p2; ([1\2]p2.returnerMark()) **
(p2.returner.mark == p2.myMark ==> turnInvariant(p1, p2)));
run {
assert !p1.gameFinished() && !p2.gameFinished();

loop_invariant (\endpoint p1; state(p1, p2, true)) ** (\endpoint p2; state(p2, p1, true));
loop_invariant (\chor p1.turn != p2.turn);
// Turned off as it is only required for branch unanimity, which is off for this test
// loop_invariant (\chor p1.turn != p2.turn);
loop_invariant (\endpoint p1; p1.turn && !p1.gameFinished() ==>
turnInvariant(p1, p2) **
([1\2]p1.returnerBound(p2)) **
Expand All @@ -45,12 +51,12 @@ choreography Main() {
([1\2]p2.returnerMark()) **
(p2.returner.mark == p2.myMark ==> turnInvariant(p2, p1)));
while (!p1.gameFinished() && !p2.gameFinished()) {
assume !p1.gameFinished() && !p2.gameFinished(); // By informal deadlock freedom proof
if (p1.turn && !p2.turn) {
p1.createNewMove();
p1.doMove();
if (p1.gameFinished()) {
p1.returner.mark := 1 - p1.myMark;
assert (\chor p1.returner.mark != p1.myMark);
}
channel_invariant
\msg.state() **
Expand All @@ -64,6 +70,7 @@ choreography Main() {
assert (\endpoint p2; p2.equalBoard(p1));
assert (\endpoint p2; !p1.gameFinished() == !p2.gameFinished());
} else {
assume !p1.turn && p2.turn; // By informal deadlock freedom proof
p2.createNewMove();
p2.doMove();
if (p2.gameFinished()) {
Expand All @@ -85,6 +92,8 @@ choreography Main() {
p2.turn := !p2.turn;
}

assume !p1.gameFinished() && !p2.gameFinished(); // By informal deadlock freedom proof

p1.finish();
p2.finish();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
This test shows that, even though one of the parties know that the branch is unanimous, we still cannot show branch unanimity.
This verifies just fine in combination with --veymont-no-branch-unanimity
*/

class Storage {
int x;
int temp;

ensures Perm(x, 1) ** x == 10 ** Perm(temp, 1);
constructor();
}

choreography Example() {
endpoint alice = Storage();
endpoint bob = Storage();

requires Perm(alice.x, 1) ** Perm[alice](bob.x, 1\2) ** Perm(alice.temp, 1);
requires Perm(bob.x, 1\2) ** Perm(bob.temp, 1);
requires alice.x == 10 && (\endpoint alice; bob.x == 10);
requires bob.x == 10;
run {
loop_invariant Perm(alice.x, 1) ** Perm[alice](bob.x, 1\2) ** Perm(alice.temp, 1);
loop_invariant (\endpoint alice; alice.x == bob.x);
loop_invariant Perm(bob.x, 1\2) ** Perm(bob.temp, 1);
loop_invariant alice.x >= 0 && bob.x >= 0;
while (alice.x > 0 && bob.x > 0) {
alice.x := alice.x - 1;
channel_invariant Perm(alice.x, 1\2) ** Perm(bob.x, 1\2);
communicate alice.x -> bob.temp;
bob.x := bob.x - 1;
}
}
}
2 changes: 2 additions & 0 deletions src/col/vct/col/ast/Node.scala
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ final class Procedure[G](
val vesuv_entry: Boolean = false,
)(val blame: Blame[CallableFailure])(implicit val o: Origin)
extends GlobalDeclaration[G] with AbstractMethod[G] with ProcedureImpl[G]
@scopes[LabelDecl]
final class VeSUVMainMethod[G](val body: Option[Statement[G]])(
val blame: Blame[CallableFailure]
)(implicit val o: Origin)
Expand Down Expand Up @@ -802,6 +803,7 @@ final class InstancePredicate[G](
final class InstanceField[G](val t: Type[G], val flags: Seq[FieldFlag[G]])(
implicit val o: Origin
) extends ClassDeclaration[G] with Field[G] with InstanceFieldImpl[G]
@scopes[LabelDecl]
final class RunMethod[G](
val body: Option[Statement[G]],
val contract: ApplicableContract[G],
Expand Down
14 changes: 13 additions & 1 deletion src/col/vct/col/ast/expr/heap/alloc/NewObjectImpl.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
package vct.col.ast.expr.heap.alloc

import vct.col.ast.expr.ExprImpl
import vct.col.ast.{NewObject, Type}
import vct.col.print.{Ctx, Doc, Precedence, Text}
import vct.col.ast.ops.NewObjectOps
import vct.col.check.{CheckContext, CheckError, TypeErrorExplanation}

trait NewObjectImpl[G] extends NewObjectOps[G] {
trait NewObjectImpl[G] extends NewObjectOps[G] with ExprImpl[G] {
this: NewObject[G] =>
override def t: Type[G] = cls.decl.classType(Seq())

override def precedence: Int = Precedence.POSTFIX
override def layout(implicit ctx: Ctx): Doc =
Text("new") <+> ctx.name(cls) <> "()"

override def check(context: CheckContext[G]): Seq[CheckError] =
super.check(context) ++
(if (cls.decl.typeArgs.nonEmpty)
Seq(TypeErrorExplanation(
this,
"This expression only supports non-generic classes",
))
else
Seq())
}
10 changes: 2 additions & 8 deletions src/col/vct/col/ast/expr/veymont/EndpointExprImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package vct.col.ast.expr.veymont

import vct.col.ast.expr.ExprImpl
import vct.col.ast.ops.EndpointExprOps
import vct.col.ast.{Endpoint, EndpointExpr, Type}
import vct.col.check.{CheckContext, CheckError, EndpointExprInChor}
import vct.col.ast.{Endpoint, EndpointExpr, TBool, TResource, Type}
import vct.col.check.CheckContext
import vct.col.print._

trait EndpointExprImpl[G] extends EndpointExprOps[G] with ExprImpl[G] {
Expand All @@ -17,10 +17,4 @@ trait EndpointExprImpl[G] extends EndpointExprOps[G] with ExprImpl[G] {
override def enterCheckContextInEndpointExpr(
context: CheckContext[G]
): Option[Endpoint[G]] = Some(this.endpoint.decl)

override def check(context: CheckContext[G]): Seq[CheckError] =
if (context.inChor)
Seq(EndpointExprInChor(this))
else
Seq()
}
10 changes: 9 additions & 1 deletion src/col/vct/col/ast/expr/veymont/MessageImpl.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package vct.col.ast.expr.veymont

import vct.col.ast.expr.ExprImpl
import vct.col.ast.ops.MessageOps
import vct.col.ast.{Message, Type}
import vct.col.check.{CheckContext, CheckError}
import vct.col.print._

trait MessageImpl[G] extends MessageOps[G] {
trait MessageImpl[G] extends MessageOps[G] with ExprImpl[G] {
this: Message[G] =>
override def layout(implicit ctx: Ctx): Doc = Text("\\msg")
override def precedence: Int = Precedence.ATOMIC
override def t: Type[G] = ref.decl.msg.t

// We don't check for being in a channel invariant here because the only other place
// where you can use a `\msg` is as the message of a communicate. In that cause, you get
// infinite recursion in the typechecker anyway, so might as well not check for this here.
override def check(context: CheckContext[G]): Seq[CheckError] =
super.check(context)
}
11 changes: 10 additions & 1 deletion src/col/vct/col/ast/expr/veymont/ReceiverImpl.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package vct.col.ast.expr.veymont

import vct.col.ast.expr.ExprImpl
import vct.col.ast.ops.ReceiverOps
import vct.col.ast.{Receiver, Type}
import vct.col.check.{CheckContext, CheckError, OnlyInChannelInvariant}
import vct.col.print._

trait ReceiverImpl[G] extends ReceiverOps[G] {
trait ReceiverImpl[G] extends ReceiverOps[G] with ExprImpl[G] {
this: Receiver[G] =>
override def layout(implicit ctx: Ctx): Doc = Text("\\receiver")
override def precedence: Int = Precedence.ATOMIC
override def t: Type[G] = ref.decl.receiver.get.decl.t

override def check(context: CheckContext[G]): Seq[CheckError] =
super.check(context) ++
(context.inCommunicateInvariant match {
case Some(_) => Seq()
case None => Seq(OnlyInChannelInvariant(this))
})
}
11 changes: 10 additions & 1 deletion src/col/vct/col/ast/expr/veymont/SenderImpl.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package vct.col.ast.expr.veymont

import vct.col.ast.expr.ExprImpl
import vct.col.ast.ops.SenderOps
import vct.col.ast.{Sender, Type}
import vct.col.check.{CheckContext, CheckError, OnlyInChannelInvariant}
import vct.col.print._

trait SenderImpl[G] extends SenderOps[G] {
trait SenderImpl[G] extends SenderOps[G] with ExprImpl[G] {
this: Sender[G] =>
override def layout(implicit ctx: Ctx): Doc = Text("\\sender")
override def precedence: Int = Precedence.ATOMIC
override def t: Type[G] = ref.decl.sender.get.decl.t

override def check(context: CheckContext[G]): Seq[CheckError] =
super.check(context) ++
(context.inCommunicateInvariant match {
case Some(_) => Seq()
case None => Seq(OnlyInChannelInvariant(this))
})
}
13 changes: 12 additions & 1 deletion src/col/vct/col/ast/node/NodeCheckOps.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package vct.col.ast.node

import vct.col.ast.{Applicable, Choreography, Declaration, Endpoint, Node}
import vct.col.ast.{
Applicable,
Choreography,
Communicate,
Declaration,
Endpoint,
Node,
}
import vct.col.check.{CheckContext, CheckError}
import vct.col.util.CurrentCheckNodeContext
import vct.result.VerificationError
Expand Down Expand Up @@ -54,6 +61,7 @@ trait NodeCheckOps[G] {
enterCheckContextCurrentParticipatingEndpoints(context),
enterCheckContextInChor(context),
enterCheckContextInEndpointExpr(context),
enterCheckContextInCommunicateInvariant(context),
enterCheckContextDeclarationStack(context),
)

Expand Down Expand Up @@ -89,6 +97,9 @@ trait NodeCheckOps[G] {
def enterCheckContextInEndpointExpr(
context: CheckContext[G]
): Option[Endpoint[G]] = context.inEndpointExpr
def enterCheckContextInCommunicateInvariant(
context: CheckContext[G]
): Option[Communicate[G]] = context.inCommunicateInvariant
def enterCheckContextDeclarationStack(
context: CheckContext[G]
): Seq[Declaration[G]] = context.declarationStack
Expand Down
21 changes: 19 additions & 2 deletions src/col/vct/col/ast/statement/veymont/CommunicateImpl.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package vct.col.ast.statement.veymont

import vct.col.ast.{Communicate, Endpoint, EndpointName, Type}
import hre.data.BitString
import vct.col.ast.declaration.DeclarationImpl
import vct.col.ast.{Communicate, Endpoint, EndpointName, Node, Type}
import vct.col.check.{CheckContext, CheckError, SeqProgParticipant}
import vct.col.print.{Ctx, Doc, Group, Nest, Text}
import vct.col.ref.Ref
import vct.col.ast.ops.CommunicateOps
import vct.col.ast.ops.{CommunicateFamilyOps, CommunicateOps}

import scala.collection.immutable.{AbstractSeq, LinearSeq}

trait CommunicateImpl[G]
extends CommunicateOps[G] with CommunicateFamilyOps[G] {
extends CommunicateOps[G]
with CommunicateFamilyOps[G]
with DeclarationImpl[G] {
comm: Communicate[G] =>
override def layout(implicit ctx: Ctx): Doc =
Text("channel_invariant") <+> Nest(invariant.show) <> ";" <+/> Group(
Expand All @@ -35,6 +41,17 @@ trait CommunicateImpl[G]
case _ => Nil
}

override def checkContextRecursor[T](
context: CheckContext[G],
f: (CheckContext[G], Node[G]) => T,
): Seq[T] =
subnodes match {
case invariant +: rest =>
f(context.withCommunicateInvariant(this), invariant) +:
rest.map(f(enterCheckContext(context), _))
case _ => ???
}

def participants: Seq[Endpoint[G]] = (sender.toSeq ++ receiver.toSeq)
.map(_.decl)

Expand Down
18 changes: 18 additions & 0 deletions src/col/vct/col/ast/unsorted/ConstructorInvocationImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package vct.col.ast.unsorted
import vct.col.ast.expr.ExprImpl
import vct.col.ast.{Class, ConstructorInvocation, TClass, Type, Variable}
import vct.col.ast.ops.ConstructorInvocationOps
import vct.col.check.{CheckContext, CheckError, TypeErrorExplanation}
import vct.col.print._

import scala.util.Try
Expand All @@ -28,4 +29,21 @@ trait ConstructorInvocationImpl[G]
}

override def precedence: Int = Precedence.ATOMIC

override def check(context: CheckContext[G]): Seq[CheckError] =
super.check(context) ++
(if (classTypeArgs.size != cls.typeArgs.size)
Seq(TypeErrorExplanation(
this,
"Number of class type arguments does not match number of type arguments at class definition",
))
else
Seq()) ++
(if (typeArgs.size != ref.decl.typeArgs.size)
Seq(TypeErrorExplanation(
this,
"Number of type arguments of constructor invocation does not match number of type arguments at constructor definition",
))
else
Seq())
}
Loading

0 comments on commit ab31613

Please sign in to comment.