diff --git a/Docs/docs/advanced/PVerifierLanguageExtensions/announcement.md b/Docs/docs/advanced/PVerifierLanguageExtensions/announcement.md new file mode 100644 index 0000000000..3471d91983 --- /dev/null +++ b/Docs/docs/advanced/PVerifierLanguageExtensions/announcement.md @@ -0,0 +1,23 @@ +# Announcing the New Verification Backend for P + +We are excited to announce the release of a new verification backend for the P programming language! This backend, which we call the P Verifier, allows you to prove that your systems behave correctly under all possible scenarios. The P Verifier is based on Mora et al. ([OOPSLA '23](https://dl.acm.org/doi/10.1145/3622876)) and uses UCLID5 (Polgreen et al., [CAV '22](https://dl.acm.org/doi/10.1007/978-3-031-13185-1_27)). + +Before the P Verifier, P helped users catch design-level bugs early in the development process through explicit-state model checking (Brooker and Desai, [Queue '25](https://doi.org/10.1145/3712057)). Given a formal model, specification, and a test driver describing a system configuration, the model checking backend systematically explores the given model in search of an execution that violates the given specification for the given system configuration. See the [Two-Phase Commit example tutorial](twopahsecommitverification.md) for an example model, specification, and three test drivers corresponding to three different system configurations (e.g., a configuration with three participants, one coordinator, and one client). P was used in this way to help AWS migrate [S3 (Simple Storage Service) from eventual to strong read-after-write consistency](https://aws.amazon.com/blogs/aws/amazon-s3-update-strong-read-after-write-consistency/). + +The new verification backend allows users to **prove** that their system design is correct for all executions over all possible system configurations. Instead of using explicit-state model checking, the new backend supports proofs by induction. Users still provide a formal model and specification. But, instead of giving a test driver, users must provide assumptions about what is possible in the system and an inductive invariant that implies that the given model satisfies the given specification under the given assumptions. The main job of the backend is to check that that given invariant is indeed inductive (all possible events in the system preserve it) and that the given invariant does indeed imply the given specification. UCLID5 accomplishes this job by way of a satisfiability modulo theories (SMT) solver, like Z3 (de Moura and BjΓΈrner, [TACAS '08](https://doi.org/10.1007/978-3-540-78800-3_24)). + +Formal verification is important to AWS’s software correctness program (Brooker and Desai, [Queue '25](https://doi.org/10.1145/3712057)). Several formal tools have had successful applications within AWS in their respective domains. The new verification backend in P gives users the benefits of correctness proofs for the domain of distributed systems design, all while preserving the existing benefits of systematic testing. + +## Getting Started and Tutorial + +To start using the P Verifier, you must install P along with the verification dependencies (UCLID5 and an SMT solver like Z3). Detailed installation instructions are available [here](install-pverifier.md); simple usage instructions are available [here](using-pverifier.md). + +To help you get acquainted with the new verification features, we have prepared a comprehensive tutorial that walks you through the formal verification of a simplified two-phase commit (2PC) protocol. This tutorial covers the key concepts and steps of using the verification backend. You can find the tutorial [here](twophasecommitverification.md). + +## Industrial Application Inside Amazon Web Services + +The two-phase commit protocol described in the tutorial is deliberately simplified to help new users get started. In that protocol, one coordinator works with a fixed set of participants to agree on a single boolean value. Industrial systems, however, call for a number of generalizations. + +We used the new verification backend to verify an industrial version of the two-phase commit protocol with the following generalizations. First, the protocol works for multiple rounds (think participants agreeing on sequences of boolean values instead of a single boolean value). Second, the participants agree on the state of a key-value store instead of boolean values. Third, the system works at the granularity of transactions (reads and writes) instead of individual values. Fourth, the key-value store is partitioned across sets of participants ("shards"). Finally, fifth, the safety property is a more general kind of consistency called [Snapshot Isolation](https://software.imdea.org/~gotsman/papers/si-podc16.pdf). We hope to release the internal details of this system and the corresponding proof of correctness at a later date. + +We look forward to your feedback. Happy verifying! diff --git a/Docs/docs/advanced/PVerifierLanguageExtensions/init-condition.md b/Docs/docs/advanced/PVerifierLanguageExtensions/init-condition.md new file mode 100644 index 0000000000..331b5b8031 --- /dev/null +++ b/Docs/docs/advanced/PVerifierLanguageExtensions/init-condition.md @@ -0,0 +1,29 @@ +# Initialization Conditions + +Initialization conditions let us constrain the kinds of systems that we consider for formal verification. You can think of these as constraints that P test harnesses have to satisfy to be considered valid. + +??? note "P Init Condition Declaration Grammar" + + ``` + initConditionDecl : + | init-condition expression; # P Init Condition Declaration + ``` + + `expression` is a boolean expression that should evaluate to true at initialization time. + +**Syntax:** `init-condition expression;` + +`expression` is a boolean expression that must be satisfied for the system to be considered valid. This is typically used with quantifiers to express constraints over sets of machines or values. + +=== "Init Condition Examples" + + ``` java + // Ensures that there's a unique machine of type coordinator + init-condition forall (m: machine) :: m == coordinator() <==> m is Coordinator; + + // Ensures that every machine in the participants set is a machine of type participant + init-condition forall (m: machine) :: m in participants() <==> m is Participant; + + // Ensures that all yesVotes tallies start empty + init-condition forall (c: Coordinator) :: c.yesVotes == default(set[machine]); + ``` diff --git a/Docs/docs/advanced/PVerifierLanguageExtensions/install-pverifier.md b/Docs/docs/advanced/PVerifierLanguageExtensions/install-pverifier.md new file mode 100644 index 0000000000..e6b19bda1b --- /dev/null +++ b/Docs/docs/advanced/PVerifierLanguageExtensions/install-pverifier.md @@ -0,0 +1,156 @@ +# Install Instructions for Amazon Linux + +PVerifier requires several dependencies to be installed. Follow the steps below to set up your environment. + +!!! success "" + After each step, please use the troubleshooting check to ensure that each installation step succeeded. + +### [Step 1] Install Java 11 + +```sh +sudo rpm --import https://yum.corretto.aws/corretto.key +sudo curl -L -o /etc/yum.repos.d/corretto.repo https://yum.corretto.aws/corretto.repo +sudo yum install java-11-amazon-corretto-devel maven +``` + +??? hint "Troubleshoot: Confirm that java is correctly installed on your machine." + ```shell + java -version + ``` + + If you get `java` command not found error, most likely, you need to add the path to `java` in your `PATH`. + +### [Step 2] Install SBT + +```sh +sudo rm -f /etc/yum.repos.d/bintray-rpm.repo || true +curl -L https://www.scala-sbt.org/sbt-rpm.repo > sbt-rpm.repo +sudo mv sbt-rpm.repo /etc/yum.repos.d/ +sudo yum install sbt +``` + +??? hint "Troubleshoot: Confirm that sbt is correctly installed on your machine." + ```shell + sbt --version + ``` + + If you get `sbt` command not found error, most likely, you need to add the path to `sbt` in your `PATH`. + +### [Step 3] Install .NET 8.0 + +Install .NET 8.0 from Microsoft: + +```sh +wget https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh +bash ./dotnet-install.sh -c 8.0 -i $HOME/.dotnet + +sudo mkdir /usr/share/dotnet/ +sudo cp -r $HOME/.dotnet/* /usr/share/dotnet/ +``` + +Then add the following line to `.zshrc` and run `source .zshrc` (or `.bashrc` if using bash): + +```sh +export PATH=$HOME/.dotnet:$HOME/.dotnet/tools:$PATH +``` + +The purpose of copying the .NET distribution into `/usr/share/dotnet` is to make standard dotnet packages available to dotnet. If you are uncomfortable modifying a system directory, you can add the following line to your `.zshrc` or `.bashrc` instead: + +```sh +export DOTNET_ROOT=$HOME/.dotnet +``` + +### [Step 4] Install Z3 + +```sh +cd ~ +git clone https://github.com/Z3Prover/z3.git +cd z3 +python scripts/mk_make.py --java +cd build; make +``` + +Then add the following lines to your `.zshrc` and run `source ~/.zshrc` (or `.bashrc` if using bash): + +```sh +export PATH=$PATH:$HOME/z3/build/ +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/z3/build/ +``` + +??? hint "Troubleshoot: Confirm that Z3 is correctly installed on your machine." + ```shell + z3 --version + ``` + + If you get `z3` command not found error, most likely, you need to add the path to `z3` in your `PATH`. + +Note: + +* Python: Building Z3 requires Python 3, but Python 2 is the default on Amazon Linux 2. If `python --version` displays version 2, try + ```sh + sudo alternatives --set python `which python3` + ``` + or + ```sh + alias python=python3 + ``` + +* G++: Building Z3 requires G++ 8 or later with support for C++20, but G++ 7 is the default on Amazon Linux 2. If `g++ --version` displays version 7, try + + ```sh + sudo yum install gcc10 gcc10-c++ + ``` + + to install gcc10-gcc and gcc10-g++ and replace the string `gcc` with `gcc10-` in `config.mk`. + +### [Step 5] Install UCLID5 + +```sh +cd ~ +git clone https://github.com/uclid-org/uclid.git +cd uclid +sbt update clean compile "set fork:=true" test # should fail some tests that use cvc5 and delphi +sbt universal:packageBin +unzip target/universal/uclid-0.9.5.zip +``` + +Then add the following line to your `.zshrc` (or `.bashrc` if using bash) and run `source ~/.zshrc`: + +```sh +export PATH=$PATH:$HOME/uclid/uclid-0.9.5/bin/ +``` + +Note: +* Tests using cvc5 and delphi are likely to fail. If you just cut and paste the build commands into your shell, this failure may inhibit running the last two commands, so just cut and paste the last two commands into your shell again. + +??? hint "Troubleshoot: Confirm that UCLID5 is correctly installed on your machine." + ```shell + uclid --help + ``` + + If you get `uclid` command not found error, most likely, you need to add the path to `uclid` in your `PATH`. + +### [Step 6] Install PVerifier + +The following steps will build P with PVerifier by running the regular P build on the PVerifier branch of the repository. + +```sh +cd ~ +git clone https://github.com/p-org/P +cd P +git checkout dev_p3.0/pverifier +root=$(pwd) +cd $root/Bld +./build.sh +dotnet tool uninstall --global P +cd $root/Src/PCompiler/PCommandLine +dotnet pack PCommandLine.csproj --configuration Release --output ./publish -p:PackAsTool=true -p:ToolCommandName=P -p:Version=2.1.3 +dotnet tool install P --global --add-source ./publish +``` + +??? hint "Troubleshoot: Confirm that PVerifier is correctly installed on your machine." + ```shell + p --version + ``` + + If you get `p` command not found error, most likely, you need to add the path to `p` in your `PATH`. diff --git a/Docs/docs/advanced/PVerifierLanguageExtensions/outline.md b/Docs/docs/advanced/PVerifierLanguageExtensions/outline.md new file mode 100644 index 0000000000..2db30d3f94 --- /dev/null +++ b/Docs/docs/advanced/PVerifierLanguageExtensions/outline.md @@ -0,0 +1,24 @@ +!!! tip "" + **We recommend that you start with the [Tutorials](tutsoutline.md) to get familiar with + the P language and its tool chain.** + +??? note "PVerifier Extension Top Level Declarations Grammar" + + ``` + topDecl: # Top-level P Program Declarations + | pureFunDecl # PureFunctionDeclaration + | initCondDecl # InitConditionPredicateDeclaration + | invariantDecl # InvariantDeclaration + | lemmaDecl # LemmaDeclaration + | proofScript # ProofScript + ; + ``` + +A PVerifier program consists P top-level declarations along with the following: + +| Top Level Declarations | Description | +| :------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | +| [Pure Functions](pure.md) | P supports declaring pure functions that do not have side effects | +| [Init Conditions](init-condition.md) | P supports declaring initial condition predicates | +| [Invariants](specification.md) | P supports declaring invariants that must hold true for the system | +| [Lemmas and Proofs](proof.md) | P supports declaring lemmas and proof scripts to verify the correctness of the system | diff --git a/Docs/docs/advanced/PVerifierLanguageExtensions/proof.md b/Docs/docs/advanced/PVerifierLanguageExtensions/proof.md new file mode 100644 index 0000000000..b64ff04fe4 --- /dev/null +++ b/Docs/docs/advanced/PVerifierLanguageExtensions/proof.md @@ -0,0 +1,97 @@ +# Lemmas and Proof Scripts + +Lemmas and proof scripts go hand in hand in the P Verifier. Lemmas allow you to decompose specifications and proof scripts allow you to relate lemmas to write larger proofs. + +## Lemmas + +Lemmas in P allow you to group related invariants together, which helps organize complex proofs, create smaller and more stable verification queries, and enable proof caching. + +??? note "P Lemma Declaration Grammar" + + ``` + lemmaDecl : + | Lemma iden { invariantsList } # P Lemma Declaration + + invariantsList : + | invariant iden: expression; + | invariantsList invariant iden: expression; + ``` + + `iden` is the name of the lemma or invariant, and `expression` is a boolean expression that should hold throughout system execution. + +**Syntax:** `Lemma lemmaName { invariant invName1: expr1; invariant invName2: expr2; ... }` + +`lemmaName` is the name of the lemma group, `invNameX` are the names of individual invariants, and `exprX` are the boolean expressions that should hold. + +=== "Lemma Declaration" + + ```java + Lemma system_config { + invariant one_coordinator: forall (m: machine) :: m == coordinator() <==> m is Coordinator; + invariant participant_set: forall (m: machine) :: m in participants() <==> m is Participant; + invariant never_commit_to_coordinator: forall (e: event) :: e is eCommit && e targets coordinator() ==> !inflight e; + // More invariants... + } + ``` + +## Proofs + +In P's verification framework, **proofs** provide a way to structure verification tasks by specifying what to verify and which lemmas to use. Proof scripts help decompose complex verification problems into smaller, more manageable parts and enable caching of intermediate results. + +??? note "P Proof Declaration Grammar" + + ``` + proofDecl : + | Proof { proofStmtList } # P Proof Declaration + + proofStmtList : + | proofStmt; + | proofStmtList proofStmt; + + proofStmt : + | prove iden; # Prove a lemma or invariant + | prove iden using iden; # Prove using another lemma + ``` + + `iden` is the name of a lemma or invariant that should be verified. + +**Syntax:** `Proof { prove target1; prove target2 using helper; ... }` + +Where `targetN` are the names of lemmas or invariants to verify, and `helper` is an optional lemma to use during verification. + +=== "Basic Proof" + + ```java + Proof { + prove system_config; // Verify system_config lemma + prove default using system_config; // Verify default P proof obligations using system_config + } + ``` + +=== "Complex Proof With Dependencies" + + ```java + Proof { + prove system_config; // First prove the system configuration lemma + prove kondo using system_config; // Use system_config to prove kondo + prove safety using kondo; // Use kondo to prove the safety property + prove default using system_config; // Verify default P obligations + } + ``` + +=== "Using Default Keyword" + + ```java + Proof { + prove lemma1; + prove lemma2; + // The special keyword "default" refers to P's built-in specifications + prove default using lemma1, lemma2; // Verify using both lemmas + } + ``` + +### Benefits of Proof Scripts + +1. **Organization**: Break down complex proofs into manageable parts +2. **Verification Stability**: They enable the verifier to construct smaller, more focused queries +3. **Caching**: Results are cached per proof step, avoiding redundant verification across runs \ No newline at end of file diff --git a/Docs/docs/advanced/PVerifierLanguageExtensions/pure.md b/Docs/docs/advanced/PVerifierLanguageExtensions/pure.md new file mode 100644 index 0000000000..7bce70f766 --- /dev/null +++ b/Docs/docs/advanced/PVerifierLanguageExtensions/pure.md @@ -0,0 +1,23 @@ +??? note "P Pure Function Declaration Grammar" + + ``` + pureFunctionDecl : + | pure iden (params)? : type; # P Pure Function Declaration + ``` + + `iden` is the name of the pure function, `params` are the parameters of the function, and `type` is the return type of the function. + +**Syntax:** `pure functionName();` or `pure functionName(param1: type1, param2: type2) : returnType;` + +`functionName` is the name of the P pure function, `param1`, `param2`, etc. are the parameters of the function, and `returnType` is the type of the value returned by the function. + +=== "Pure Function Declarations" + + ``` java + // declaration of pure functions with no parameters + pure participants(): set[machine]; + pure coordinator(): machine; + + // declaration of pure functions with parameters + pure preference(m: machine) : bool; + ``` diff --git a/Docs/docs/advanced/PVerifierLanguageExtensions/specification.md b/Docs/docs/advanced/PVerifierLanguageExtensions/specification.md new file mode 100644 index 0000000000..76f2343f24 --- /dev/null +++ b/Docs/docs/advanced/PVerifierLanguageExtensions/specification.md @@ -0,0 +1,109 @@ +# Specifications + +The P verifier adds three kinds of specifications: global invariants, loop invariants, and function contracts. We cover each type in its own subsection below. + +## Global Invariants + +Global invariants are properties that should hold true throughout the execution of the system. + +**Syntax:** `invariant [name:] expression;` + +`name` is an optional name for the invariant, and `expression` is a boolean expression that should hold true in all reachable states. + +??? note "Grammar" + ``` + invariant [name:] expression; # Global Invariant Declaration + ``` + +=== "Basic Invariants" + + ``` java + // Simple invariant that checks a coordinator property + invariant one_coordinator: forall (m: machine) :: m == coordinator() <==> m is Coordinator; + + // Invariant to ensure messages are directed to the right machines + invariant never_req_to_coordinator: forall (e: event) :: e is eVoteReq && e targets coordinator() ==> !inflight e; + + // Safety property invariant + invariant safety: forall (p1: Participant) :: p1 is Accepted ==> (forall (p2: Participant) :: preference(p2)); + ``` + +## Loop Invariants + +Loop invariants are properties that should hold true during the execution of loops. + +**Syntax:** `invariant expression;` + +`expression` is a boolean expression that should hold true throughout the execution of the loop. + +??? note "Grammar" + ``` + invariant expression; # Loop Invariant + ``` + +=== "Loop Invariants" + + ``` java + foreach (p in participants()) + invariant forall new (e: event) :: forall (m: machine) :: e targets m ==> m in participants(); + invariant forall new (e: event) :: e is eVoteReq; + { + send p, eVoteReq; + } + ``` + +## Function Contracts + +Function contracts specify preconditions, postconditions, and return value names for functions. + +**Syntax:** `requires expression;` and `ensures expression;` and `return (name: type);` + +`requires` specifies a precondition that must be true before the function is executed, `ensures` specifies a postcondition that must be true after the function is executed, and `return` binds the return value to name that you can write pre- and postconditions over. + +??? note "Grammar" + ``` + requires expression; # Function Precondition + ensures expression; # Function Postcondition + return (name: type); # Function Return Binding + ``` + +=== "Function Contracts" + + ``` java + fun RandomParticipant(s: set[machine]) + return (x: machine); + ensures x in s; + requires NotEmpty(s); // where NotEmpty is a helper function + ``` + +## Special Predicates + +P provides special predicates for specifying message state: + +- `inflight e`: True if message `e` has been sent but not yet received +- `sent e`: True if message `e` has been sent (regardless of whether it has been received) + +### Quantifiers + +P supports several quantifier expressions for specifying properties over collections: + +- `forall (x: type) :: expression`: True if the expression is true for all instances of the specified type +- `exists (x: type) :: expression`: True if the expression is true for at least one instance of the specified type +- `forall new (x: type) :: expression`: Similar to `forall`, but specifically quantifies over newly sent events in the body of a loop. Can only be used in loop invariants. + +=== "Quantifier Examples" + + ``` java + // Universal quantification + invariant all_participants_ready: forall (p: Participant) :: p.status == READY; + + // Existential quantification + invariant leader_exists: exists (p: Participant) :: p.isLeader; + + // Quantifying over new events in a loop + foreach (p in participants()) + invariant forall new (e: event) :: e is eVoteReq; + { + send p, eVoteReq; + } + ``` \ No newline at end of file diff --git a/Docs/docs/advanced/PVerifierLanguageExtensions/twophasecommitverification.md b/Docs/docs/advanced/PVerifierLanguageExtensions/twophasecommitverification.md new file mode 100644 index 0000000000..7f5889ef66 --- /dev/null +++ b/Docs/docs/advanced/PVerifierLanguageExtensions/twophasecommitverification.md @@ -0,0 +1,388 @@ +# Introduction to Formal Verification in P + +This tutorial describes the formal verification features of P through an example. We assume that the reader has P installed along with the verification dependencies (i,e., UCLID5 and Z3). Installation instructions are available [here](install-pverifier.md). + +When using P for formal verification, our goal is to show that no execution of any test driver will violate a specification. To do this, we will rely on proofs by induction---more on that later. This backend is different from P's explicit state model checker, which you are accustomed to using. These differences can influence the modeling decisions you make. + +To get a sense of these differences, and to cover the new features in P for verification, we will verify a simplified 2PC protocol. The P tutorial already describes a 2PC protocol, but we will make some different modeling choices that will make the verification process easier. In particular, we will follow the modeling decisions made by [Zhang et al.](https://www.usenix.org/system/files/osdi24-zhang-nuda.pdf) in their running example. + +## 2PC Model + +The 2PC protocol consists of two types of machines: coordinator and participant. There is a single coordinator, who receives requests from the outside world, and a set of participants, that must agree on whether to accept or reject the request. If any participant wants to reject the request, they must all agree to reject the request; if all participants want to accept the request, then they must all agree to accept the request. The job of the coordinator is to mediate this agreement. +To accomplish this, the system executes the following steps: + +1. The coordinator sends a message to all participants which asks the participants to vote on the request in question. +2. When a participant receives this vote request message, they reply with their vote. +3. When the coordinator receives a "no" vote---indicating that a participant wants to reject the request---it will send a message to all participants telling them to abort the request. +4. Otherwise, the coordinator tallies the "yes" vote. If all participants have voted "yes," then the coordinator sends a message to all participants telling them to commit the request. +5. When a participant receives either a commit or abort message from the coordinator, it store the decision locally. + +This system is extremely easy to model in P. Before defining the machines in the system, let's declare the five types of messages that are sent. + +``` +type tVoteResp = (source: machine); // we will keep track of who sent a vote +event eVoteReq; +event eYes: tVoteResp; +event eNo: tVoteResp; +event eAbort; +event eCommit; +``` + +### The Coordinator + +The coordinator is a machine with a single variable that we will use to keep track of votes. This machine has four states: one to kickoff the voting process, one to collect the votes; and two to indicate if the voting process resulted in a commit or an abort. + +``` +machine Coordinator +{ + var yesVotes: set[machine]; // set of machines that have voted yes + + start state Init {...} + state WaitForResponses {...} + state Committed {ignore eYes, eNo;} + state Aborted {ignore eYes, eNo;} +} +``` + +In the above code we omitted the internal details of states. Now let's go through these details one by one. First, the start state, called Init, contains a single for-loop that sends a vote request message to all the participants in the system. We use a function called "participants" to get the set of active participants. + +``` + start state Init { + entry { + var p: machine; + foreach (p in participants()) + { + send p, eVoteReq; // broadcast vote request to all participants + } + goto WaitForResponses; // move to WaitForResponses state + } + } +``` + +Second, the `WaitForResponses` state has two event handlers, one for each type of vote that participants can cast. + +``` +state WaitForResponses { + on eYes do (resp: tVoteResp) {...} + on eNo do (resp: tVoteResp) {...} +} +``` + +The details of when the coordinator receives "no" votes is simpler, so lets begin there. When the coordinator receives a "no" vote, it sends an abort message to all participants. + +``` +on eNo do (resp: tVoteResp) { + var p: machine; + foreach (p in participants()) + { + send p, eAbort; // broadcast abort to all participants + } + goto Aborted; +} +``` + +When a coordinator receives a "yes" vote, it will tally the vote and only broadcast a commit message to all participants if all participants have voted yes. + +``` +on eYes do (resp: tVoteResp) { + var p: machine; + yesVotes += (resp.source); + if (yesVotes == participants()) { // if everyone voted "yes" + foreach (p in participants()) + { + send p, eCommit; // broadcast commit message + } + goto Committed; // move to committed state: request was accepted + } +} +``` + +The final two states, `Committed` and `Aborted` will remain empty for now: we just want to use them to indicate the state of a request. + +### Participants + +Participants are slightly simpler. They consist of two states: one that does all the work, and two that we use to indicate that a request has been committed or aborted, just like we did for the coordinator. + +``` +machine Participant { + start state Undecided {...} + state Accepted {ignore eVoteReq, eCommit, eAbort;} + state Rejected {ignore eVoteReq, eCommit, eAbort;} +} +``` + +The main state, called "Undecided," has three event handlers: one for responding to vote requests, and two simpler ones for handling commit and abort messages. + +``` +on eVoteReq do { + // vote based on your preference! + if (preference(this)) { + send coordinator(), eYes, (source = this,); + } else { + send coordinator(), eNo, (source = this,); + } +} + +on eCommit do {goto Accepted;} +on eAbort do {goto Rejected;} +``` + +We use a function called "preference" to decide whether to vote yes or no on a transaction. We also use a function, "coordinator," to get the address of the coordinator machine. + +## Pure Functions + +The 2PC model described above uses three special functions, `participants`, `coordinator`, and `preference`, that capture the set of participants, the coordinator in charge, and the preference of individual participants for the given request. In this simple system, there is always one coordinator and a fixed set of participants, but we want the proof to work for any function that satisfies those conditions. In P, we can use the new concept of "pure" functions to model this (what SMT-LIB calls functions). Specifically, we can declare the three special functions as follows. + +``` +pure participants(): set[machine]; +pure coordinator(): machine; +pure preference(m: machine) : bool; +``` + +The participants function is a pure function with no body that takes no argument and returns a set of machines. The coordinator function is similar but only returns a single machine. The preference function, which also has no body, takes a machine and returns a preference. We call these functions "pure" because they can have no side-effects and behave like mathematical functions (e.g., calling the same pure function twice with the same arguments must give the same result). When pure functions do not have bodies, they are like foreign functions that we can guarantee don't have side-effects. When pure functions do have bodies, the bodies must be side-effect-free expressions. + +## Initialization Conditions + +We want our model to capture many different system configurations (e.g., number of participants) but not all configurations are valid. For example, we want to constrain the `participants` function to only point to participant machines. Initialization conditions let us constrain the kinds of systems that we consider. You can think of these as constraints that P test harnesses have to satisfy to be considered valid. + +In our 2PC model, for example, we can state that, at initialization, there is a unique machine of type coordinator, and the `coordinator` function points to that machine; and every machine in the participants set is a machine of type participant. + +``` +init-condition forall (m: machine) :: m == coordinator() == m is Coordinator; +init-condition forall (m: machine) :: m in participants() == m is Participant; +``` + +We can also state that all `yesVotes` tallies start empty. + +``` +init-condition forall (c: Coordinator) :: c.yesVotes == default(set[machine]); +``` + +When we write a proof of correctness later in this tutorial, we will be restricting the systems that we consider to those that satisfy the initialization conditions listed above. + +## Quantifiers and Machine Types + +Our initialization conditions contain two new P features: the `init` keyword, and quantified expressions (`forall` and `exists`). Even more interesting, one quantifier above is over a machine subtype (`coordinator`). + +In P, the only way to dereference a machine variable inside of a specification (like the `init-condition`s above) is by specifically quantifying over that machine type. In other words, `forall (c: Coordinator) :: c.yesVotes == default(set[machine])` is legal but `forall (c: machine) :: c.yesVotes == default(set[machine])` is not, even though they might appear to be similar. The reason for this is that selecting (using the `.` operator) on an incorrect subtype (e.g., trying to get `yesVotes` from a participant machine) is undefined. Undefined behavior in formal verification can lead to surprising results that can be really hard to debug, so in P we syntactically disallow this kind of undefined behavior altogether. + +## P's Builtin Specifications And Our First Proof Attempt + +Using the code described above, and by setting the target to `PVerifier` in the `.pproj` file, you can now run the verification engine for the first time (execute `p compile`). This run will result in a large list of failures, containing items like `❌ Failed to verify that Coordinator never receives eVoteReq in Init`. These failures represent P's builtin requirements that all events are handled. They also give us a glimpse into how verification by induction works. + +Proofs by induction consist of a base case check and an inductive step check. The inductive step is more interesting and so we will focus our attention there. The high level idea is that you assume you are in a _good_ state of the system (I will describe what I mean by _good_ in the next section), and then you check if taking any step of the system will again land you in a _good_ state. in P, taking a step of the system means executing any event handler in any machine. + +When we ran our verification engine it reported that it failed to prove that all of P's builtin specifications were satisfied. Specifically, the verification engine gave us a list of all the builtin specifications that it failed to prove, like ``❌ Failed to verify that Coordinator never receives eVoteReq in Init``. + +The verification engine is unable to prove these properties not because the system is incorrect, but rather because it needs help from the user to complete it's proof: it needs the user to define the _good_ states. More formally, it needs the user to define an inductive invariant that implies that no builtin P specification is violated. + +## Invariants And Our First Proof + +Users can provide invariants to P using the `invariant` keyword. The goal is to find a set of invariants whose conjunction is inductive and implies the desired property. For now, the desired property is that no builtin P specification is violated. + +In the 2PC model, the following 10 invariants are sufficient to prove that no builtin P specification is violated. + +``` +invariant one_coordinator: forall (m: machine) :: m == coordinator() == m is Coordinator; +invariant participant_set: forall (m: machine) :: m in participants() == m is Participant; + +invariant never_commit_to_coordinator: forall (e: event) :: e is eCommit && e targets coordinator() ==> !inflight e; +invariant never_abort_to_coordinator: forall (e: event) :: e is eAbort && e targets coordinator() ==> !inflight e; +invariant never_req_to_coordinator: forall (e: event) :: e is eVoteReq && e targets coordinator() ==> !inflight e; +invariant never_yes_to_participant: forall (e: event, p: Participant) :: e is eYes && e targets p ==> !inflight e; +invariant never_yes_to_init: forall (e: event, c: Coordinator) :: e is eYes && e targets c && c is Init ==> !inflight e; +invariant never_no_to_participant: forall (e: event, p: Participant) :: e is eNo && e targets p ==> !inflight e; +invariant never_no_to_init: forall (e: event, c: Coordinator) :: e is eNo && e targets c && c is Init ==> !inflight e; +invariant req_implies_not_init: forall (e: event, c: Coordinator) :: e is eVoteReq && c is Init ==> !inflight e; +``` + +The first two invariants state that, over the run of the system, our assumptions about the `coordinator` and `participants` functions remain satisfied. The next eight invariants ensure that messages target the correct kind of machine. For example, the invariant called `never_req_to_coordinator` says that there is never a vote request message going to a coordinator. These invariants use the special predicate `inflight` which is true iff the argument message has been sent but not received. P also supports a similar predicate called `sent` which is true iff the argument message has been sent. + +After adding these invariants, we can re-run the verification engine to get the following output. + +``` +πŸŽ‰ Verified 10 invariants! +βœ… one_coordinator +βœ… participant_set +βœ… never_commit_to_coordinator +βœ… never_abort_to_coordinator +βœ… never_req_to_coordinator +βœ… never_yes_to_participant +βœ… never_yes_to_init +βœ… never_no_to_participant +βœ… never_no_to_init +βœ… req_implies_not_init +❌ Failed to verify 30 properties! +❓ Failed to verify invariant never_commit_to_coordinator at PSrc/System.p:13:5 +... +❓ Failed to verify invariant never_no_to_participant at PSrc/System.p:40:12 +``` + +What went wrong? Well, loops are tricky to reason about, so P requires users to provide loop invariants. You can think of these as summaries of what the loop does. P uses these summaries to prove other properties and checks that the loops actually abide by the given summaries. For example, we can add loop invariants to the first broadcast loop that we wrote---the one that sends out vote requests---as follows. + +``` +foreach (p in participants()) + invariant forall new (e: event) :: forall (m: machine) :: e targets m ==> m in participants(); + invariant forall new (e: event) :: e is eVoteReq; +{ + send p, eVoteReq; +} +``` + +After adding similar loop invariants to all three loops in the model, we can re-run the verification engine to get the following output. + +``` +πŸŽ‰ Verified 10 invariants! +βœ… one_coordinator +βœ… participant_set +βœ… never_commit_to_coordinator +βœ… never_abort_to_coordinator +βœ… never_req_to_coordinator +βœ… never_yes_to_participant +βœ… never_yes_to_init +βœ… never_no_to_participant +βœ… never_no_to_init +βœ… req_implies_not_init +βœ… default P proof obligations +``` + +Notice that our initial model uses `ignore` statements that we did not describe when introducing the model. If we remove these statements, the verification engine will not be able to prove that no builtin P specification is violated. In some cases, that is because the ignore statements are necessary. For example, it is possible for the coordinator to receive a "yes" or "no" vote when it is in the `Aborted` state. The `ignore` keyword lets us tell the verifier that we have thought of these cases. + +## Invariant Groups, Proof Scripts, and Proof Caching + +When writing larger proofs, it will become useful to group invariants into lemmas, and then to tell the verifier how to use these lemmas for its proof checking. This helps the user organize their proofs; it helps the verifier construct smaller, more stable queries; and it helps avoid checking the same queries over and over, through caching. + +For example, we will write more complex specifications for the 2PC protocol but we will continue to verify that the builtin P specifications are satisfied. Instead of having to execute the verifier over and over to check these builtin properties as we build our larger proofs, we want to cache these results. Futhermore, we don't want any new specifications to interfere with these proof results---unless the model changes there really is no reason to look for a different proof of these same properties. + +P allows users to define invariant groups, like the following. + +``` +Lemma system_config { + invariant one_coordinator: forall (m: machine) :: m == coordinator() == m is Coordinator; + invariant participant_set: forall (m: machine) :: m in participants() == m is Participant; + invariant never_commit_to_coordinator: forall (e: event) :: e is eCommit && e targets coordinator() ==> !inflight e; + invariant never_abort_to_coordinator: forall (e: event) :: e is eAbort && e targets coordinator() ==> !inflight e; + invariant never_req_to_coordinator: forall (e: event) :: e is eVoteReq && e targets coordinator() ==> !inflight e; + invariant never_yes_to_participant: forall (e: event, p: Participant) :: e is eYes && e targets p ==> !inflight e; + invariant never_yes_to_init: forall (e: event, c: Coordinator) :: e is eYes && e targets c && c is Init ==> !inflight e; + invariant never_no_to_participant: forall (e: event, p: Participant) :: e is eNo && e targets p ==> !inflight e; + invariant never_no_to_init: forall (e: event, c: Coordinator) :: e is eNo && e targets c && c is Init ==> !inflight e; + invariant req_implies_not_init: forall (e: event, c: Coordinator) :: e is eVoteReq && c is Init ==> !inflight e; +} +``` + +These are all the same invariants we used above but now grouped inside a lemma called `system_config`. We can then use this lemma to decompose our proof using a proof script. + +``` +Proof { + prove system_config; + prove default using system_config; +} +``` + +This proof script has two steps. First it says that we need to prove that the lemma always holds (that the conjunction of the invariants are inductive for this model). Second we prove that P's default specifications always hold and tell the solver to use the `system_config` lemma to do so. When we run the verification engine again, we will get the following output. + +``` +πŸŽ‰ Verified 10 invariants! +βœ… system_config: one_coordinator +βœ… system_config: participant_set +βœ… system_config: never_commit_to_coordinator +βœ… system_config: never_abort_to_coordinator +βœ… system_config: never_req_to_coordinator +βœ… system_config: never_yes_to_participant +βœ… system_config: never_yes_to_init +βœ… system_config: never_no_to_participant +βœ… system_config: never_no_to_init +βœ… system_config: req_implies_not_init +βœ… default P proof obligations +``` + +Notice that the first time you run this verification it will take much longer than the second time. That is because the proof is cached and the solver is not executed the second time. If we don't change our model or our lemma, these queries will never be executed again. + +## First 2PC Specification and Proof + +Proving that the builtin P specifications are satisfied is all well and good, but it isn't exactly the most interesting property. Zhang et al. provide a more exciting specification that we can verify ("2PC-Safety"). Translated into P, their specification looks like the following invariant. + +``` +invariant safety: forall (p1: Participant) :: p1 is Accepted ==> (forall (p2: Participant) :: preference(p2)); +``` + +This invariant says that if any participant is in the accepted state, then every participant must have wanted to accept the request. Zhang et al. also provide a set of inductive invariants that help prove the safety property above (Fig. 5 in their paper). For our model in P, this set of invariants looks like the following. + +``` +Lemma kondo { + invariant a1a: forall (e: eYes) :: inflight e ==> e.source in participants(); + invariant a1b: forall (e: eNo) :: inflight e ==> e.source in participants(); + invariant a2a: forall (e: eYes) :: inflight e ==> preference(e.source); + invariant a2b: forall (e: eNo) :: inflight e ==> !preference(e.source); + invariant a3b: forall (e: eAbort) :: inflight e ==> coordinator() is Aborted; + invariant a3a: forall (e: eCommit) :: inflight e ==> coordinator() is Committed; + invariant a4: forall (p: Participant) :: p is Accepted ==> coordinator() is Committed; + invariant a5: forall (p: Participant, c: Coordinator) :: p in c.yesVotes ==> preference(p); + invariant a6: coordinator() is Committed ==> (forall (p: Participant) :: p in participants() ==> preference(p)); +} +``` + +Given the desired safety property that we want to prove and the set of invariants that helps us prove it, we can write the following proof script in P that checks that the proof by induction passes. + +``` +Proof { + prove system_config; + prove kondo using system_config; + prove safety using kondo; + prove default using system_config; +} +``` + +Running the verification engine on this file will produce the following. + +``` +πŸŽ‰ Verified 20 invariants! +βœ… safety +βœ… system_config: one_coordinator +βœ… system_config: participant_set +βœ… system_config: never_commit_to_coordinator +βœ… system_config: never_abort_to_coordinator +βœ… system_config: never_req_to_coordinator +βœ… system_config: never_yes_to_participant +βœ… system_config: never_yes_to_init +βœ… system_config: never_no_to_participant +βœ… system_config: never_no_to_init +βœ… system_config: req_implies_not_init +βœ… kondo: a1a +βœ… kondo: a1b +βœ… kondo: a2a +βœ… kondo: a2b +βœ… kondo: a3b +βœ… kondo: a3a +βœ… kondo: a4 +βœ… kondo: a5 +βœ… kondo: a6 +βœ… default P proof obligations +``` + +Showing that the verification passes. Notice that if you remove any of the invariants from the lemmas, like say `a5` in `kondo`, the proof will fail with the following output. + +``` +❌ Failed to verify 1 properties! +❓ Failed to verify invariant kondo: a6 at PSrc/System.p:27:12 +``` + +## Recap and Next Steps + +In this tutorial, we formally verified a simplified 2PC protocol in P. The full, final code for the verification is available [here](https://github.com/p-org/P/tree/dev_p3.0/pverifier/Tutorial/Advanced/2_TwoPhaseCommitVerification/Single/PSrc/System.p). + +Our proof followed the running example of Zhang et al. but also included the verification of builtin P specifications. Along the way, we introduced the following new P keywords and concepts. + +1. `pure` functions; +2. `init-condition` predicates; +3. quantifiers; +4. proofs by induction; +5. `invariant`s; +6. `inflight` and `sent`; +6. loop invariants; +7. `lemma`s as invariant groups; +8. `proof` scripts; and +9. proof caching. + +In a future tutorial, we will expand on this simple 2PC protocol by introducing a key-value store and a monitor specification. diff --git a/Docs/docs/advanced/PVerifierLanguageExtensions/using-pverifier.md b/Docs/docs/advanced/PVerifierLanguageExtensions/using-pverifier.md new file mode 100644 index 0000000000..6fac3e61df --- /dev/null +++ b/Docs/docs/advanced/PVerifierLanguageExtensions/using-pverifier.md @@ -0,0 +1,64 @@ +!!! check "" + Before moving forward, we assume that you have successfully installed + the [PVerifier](install-pverifier.md). + +In this section, we provide an overview of the steps involved in verifying a P program using the [two-phase commit](../tutorial/twophasecommit.md) example in Tutorials. + +??? info "Get the Two-Phase Commit Example Locally" + We will use the [TwoPhaseCommit](https://github.com/p-org/P/tree/master/Tutorial/2_TwoPhaseCommit) example from Tutorial folder in P repository to describe the process of verifying a P program. Please clone the P repo and navigate to the + TwoPhaseCommit example in Tutorial. + + Clone P Repo locally: + ```shell + git clone https://github.com/p-org/P.git + ``` + Navigate to the TwoPhaseCommit examples folder: + ```shell + cd

/Tutorial/2_TwoPhaseCommit + ``` + +### Verifying a P program + +To verify a P program using the PVerifier, you need to: + +1. Configure your project to use PVerifier as the target in your `.pproj` file +2. Compile the project using the P compiler + +This process follows the same workflow described in [Using P](../../getstarted/usingp.md), except that we specify `PVerifier` as the backend instead of other targets like `CSharp` or `Java`. + +#### Executing the verification + +After setting the target to `PVerifier` in your project file, run the P compiler with the following command: + +```shell +p compile +``` + +The compiler will generate verification code and automatically invoke the PVerifier to check your model against the specifications defined in your P program. The verification results will be displayed in the terminal, showing whether the properties are satisfied or if there are any violations along with counterexample traces. + +Running the verification engine on the Two-Phase Commit example will produce the following. + +``` +πŸŽ‰ Verified 20 invariants! +βœ… system_config_one_coordinator +βœ… system_config_participant_set +βœ… system_config_never_commit_to_coordinator +βœ… system_config_never_abort_to_coordinator +βœ… system_config_never_req_to_coordinator +βœ… system_config_never_yes_to_participant +βœ… system_config_never_yes_to_init +βœ… system_config_never_no_to_participant +βœ… system_config_never_no_to_init +βœ… system_config_req_implies_not_init +βœ… kondo_a1a +βœ… kondo_a1b +βœ… kondo_a2a +βœ… kondo_a2b +βœ… kondo_a3b +βœ… kondo_a3a +βœ… kondo_a4 +βœ… kondo_a5 +βœ… kondo_a6 +βœ… safety +βœ… default P proof obligations +``` diff --git a/Src/PCompiler/CompilerCore/Backend/Debugging/IrToPseudoP.cs b/Src/PCompiler/CompilerCore/Backend/Debugging/IrToPseudoP.cs index 23c7968fb9..91d48c595b 100644 --- a/Src/PCompiler/CompilerCore/Backend/Debugging/IrToPseudoP.cs +++ b/Src/PCompiler/CompilerCore/Backend/Debugging/IrToPseudoP.cs @@ -100,9 +100,6 @@ private void WriteTree(IPAST tree) WriteStmt(machine.IsSpec ? "spec " : "", "machine ", machine); - var machineAssume = machine.Assume?.ToString() ?? "max"; - var machineAssert = machine.Assert?.ToString() ?? "max"; - WriteStmt(" assert ", machineAssert, " assume ", machineAssume); WriteStmt(" receives ", WriteEventSet(machine.Receives)); WriteStmt(" sends ", WriteEventSet(machine.Sends)); if (machine.IsSpec) @@ -150,10 +147,6 @@ private void WriteTree(IPAST tree) case Event pEvent: WriteStmt("event ", pEvent, - " assert ", - pEvent.Assert, - " assume ", - pEvent.Assume, " : ", pEvent.PayloadType, ";"); @@ -175,6 +168,10 @@ private void WriteTree(IPAST tree) WriteStmt("assert ", assertStmt.Assertion, ", \"", assertStmt.Message, "\";"); break; + case AssumeStmt assumeStmt: + WriteStmt("assume ", assumeStmt.Assumption, ", \"", assumeStmt.Message, "\";"); + break; + case AssignStmt assignStmt: WriteStmt(assignStmt.Location, " = ", assignStmt.Value, ";"); break; diff --git a/Src/PCompiler/CompilerCore/Backend/IRTransformer.cs b/Src/PCompiler/CompilerCore/Backend/IRTransformer.cs index 1aa12b676e..5f2edd8b52 100644 --- a/Src/PCompiler/CompilerCore/Backend/IRTransformer.cs +++ b/Src/PCompiler/CompilerCore/Backend/IRTransformer.cs @@ -363,6 +363,22 @@ private List SimplifyStatement(IPStmt statement) }).ToList())); return assertDeps.Concat(new List{ifStmtForAssert}) .ToList(); + + case AssumeStmt assumeStmt: + (var assumeExpr, var assumeDeps) = SimplifyExpression(assumeStmt.Assumption); + (var amessageExpr, var amessageDeps) = SimplifyExpression(assumeStmt.Message); + if (assumeExpr is BoolLiteralExpr) + { + return assumeDeps.Concat(amessageDeps).Concat(new []{new AssumeStmt(location, assumeExpr, amessageExpr)}).ToList(); + } + + var aifStmtForAssert = new IfStmt(location, assumeExpr, new NoStmt(location), new CompoundStmt( + location, amessageDeps.Concat(new[] + { + new AssumeStmt(location, assumeExpr, amessageExpr) + }).ToList())); + return assumeDeps.Concat(new List{aifStmtForAssert}) + .ToList(); case AssignStmt assignStmt: (var assignLV, var assignLVDeps) = SimplifyLvalue(assignStmt.Location); @@ -433,7 +449,6 @@ private List SimplifyStatement(IPStmt statement) new CompoundStmt(ifStmt.ElseBranch.SourceLocation, elseBranch)) }) .ToList(); - case AddStmt addStmt: var (addVar, addVarDeps) = SimplifyLvalue(addStmt.Variable); var (addVal, addValDeps) = SimplifyArgPack(new[] { addStmt.Value }); diff --git a/Src/PCompiler/CompilerCore/Backend/PVerifier/CompilationContext.cs b/Src/PCompiler/CompilerCore/Backend/PVerifier/CompilationContext.cs new file mode 100644 index 0000000000..5d46170946 --- /dev/null +++ b/Src/PCompiler/CompilerCore/Backend/PVerifier/CompilationContext.cs @@ -0,0 +1,11 @@ +namespace Plang.Compiler.Backend.PVerifier; + +internal class CompilationContext : CompilationContextBase +{ + public CompilationContext(ICompilerConfiguration job) : base(job) + { + FileName = $"{ProjectName}.ucl"; + } + + public string FileName { get; set; } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/Backend/PVerifier/Uclid5CodeGenerator.cs b/Src/PCompiler/CompilerCore/Backend/PVerifier/Uclid5CodeGenerator.cs new file mode 100644 index 0000000000..4d618365d0 --- /dev/null +++ b/Src/PCompiler/CompilerCore/Backend/PVerifier/Uclid5CodeGenerator.cs @@ -0,0 +1,2228 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using LiteDB; +using Plang.Compiler.TypeChecker; +using Plang.Compiler.TypeChecker.AST; +using Plang.Compiler.TypeChecker.AST.Declarations; +using Plang.Compiler.TypeChecker.AST.Expressions; +using Plang.Compiler.TypeChecker.AST.Statements; +using Plang.Compiler.TypeChecker.AST.States; +using Plang.Compiler.TypeChecker.Types; + +namespace Plang.Compiler.Backend.PVerifier; + +public class PVerifierCache +{ + public string Reply { get; set; } + public byte[] Checksum { get; set; } +} + +public class PVerifierCodeGenerator : ICodeGenerator +{ + private CompilationContext _ctx; + private CompiledFile _src; + private HashSet _optionsToDeclare; + private HashSet _chooseToDeclare; + private HashSet _setCheckersToDeclare; + private Dictionary> _specListenMap; // keep track of the procedure names for each event + private Dictionary _fileToProofCommands; + private Dictionary> _proofCommandToFiles; + private List _commands; + private Dictionary> _invariantDependencies; + private HashSet _provenInvariants; + private Scope _globalScope; + private Invariant _defaultInv; + + public bool HasCompilationStage => true; + + private byte[] ComputeCheckSum(FileStream stream) + { + string file_content = new StreamReader(stream).ReadToEnd(); + // ignore `set_solver_option` statements + file_content = Regex.Replace(file_content, @"set_solver_option\((.*)\);", "", RegexOptions.Multiline); + using (var md5 = MD5.Create()) + { + return md5.ComputeHash(Encoding.UTF8.GetBytes(file_content)); + } + } + + public void Compile(ICompilerConfiguration job) + { + HashSet failMessages = []; + HashSet succeededInv = []; + HashSet failedInv = []; + var missingDefault = true; + + // Open database (or create if doesn't exist) + var db = new LiteDatabase(Path.Join(job.OutputDirectory.FullName, ".verifier-cache.db")); + // Get a collection (or create, if doesn't exist) + var qCollection = db.GetCollection("qCollection"); + + int parallelism = job.Parallelism; + if (parallelism == 0) + { + parallelism = Environment.ProcessorCount; + } + + foreach (var cmd in _commands) + { + if (cmd.Name.Contains("default")) + { + missingDefault = false; + } + + job.Output.WriteInfo($"Proving: {cmd.Name}"); + Dictionary checklist = _proofCommandToFiles[cmd].ToDictionary(x => x, x => false); + Dictionary tasks = []; + + // prefill (check cache for everything, but only spin up `parallelism` number of runs + foreach (var f in checklist) + { + using (var stream = File.OpenRead(Path.Join(job.OutputDirectory.FullName, f.Key))) + { + var checksum = ComputeCheckSum(stream); + var hit = qCollection.FindOne(x => x.Checksum == checksum); + if (hit != null) + { + checklist[f.Key] = true; + var currUndefs = Regex.Matches(hit.Reply, @"UNDEF -> (.*), line (\d+)"); + var currFails = Regex.Matches(hit.Reply, @"FAILED -> (.*), line (\d+)"); + var (invs, failed, msgs) = + AggregateResults(job, f.Key, currUndefs.ToList(), currFails.ToList()); + succeededInv.UnionWith(invs); + failedInv.UnionWith(failed); + failMessages.UnionWith(msgs); + } + else if (tasks.Count < parallelism) + { + var args = new[] { "-M", f.Key }; + tasks.Add(f.Key, Compiler.NonBlockingRun(job.OutputDirectory.FullName, "uclid", args)); + } + } + } + + var numCompleted = checklist.Values.Sum(x => x ? 1 : 0); + Console.Write($"\rπŸ” Checked {numCompleted}/{checklist.Count} goals..."); + + // fetch + while (checklist.ContainsValue(false)) + { + Dictionary newTasks = []; + foreach (var r in tasks) + { + if (!r.Value.HasExited || checklist[r.Key]) continue; + // checklist is true if we've already done this + checklist[r.Key] = true; + + var exitCode = Compiler.WaitForResult(r.Value, out var stdout, out var stderr); + if (exitCode != 0) + { + throw new TranslationException($"Verifying generated UCLID5 code FAILED ({r.Key})!\n" + + $"{stdout}\n" + + $"{stderr}\n"); + } + + r.Value.Kill(); + + var currUndefs = Regex.Matches(stdout, @"UNDEF -> (.*), line (\d+)"); + var currFails = Regex.Matches(stdout, @"FAILED -> (.*), line (\d+)"); + var (invs, failed, msgs) = AggregateResults(job, r.Key, currUndefs.ToList(), currFails.ToList()); + succeededInv.UnionWith(invs); + failedInv.UnionWith(failed); + failMessages.UnionWith(msgs); + // cache the results only when no invariant times out + if (currUndefs.Count == 0) + { + // add stdout to the database along with the corresponding checksum of the uclid query + using var stream = File.OpenRead(Path.Join(job.OutputDirectory.FullName, r.Key)); + var newResult = new PVerifierCache + { + Reply = stdout, + Checksum = ComputeCheckSum(stream) + }; + qCollection.Insert(newResult); + } + + // find someone that hasn't run and isn't running and run it + var newTask = checklist.FirstOrDefault(x => + x.Value == false && !tasks.ContainsKey(x.Key) && !newTasks.ContainsKey(x.Key)).Key; + if (newTask == null) continue; + var args = new[] { "-M", newTask }; + newTasks.Add(newTask, Compiler.NonBlockingRun(job.OutputDirectory.FullName, "uclid", args)); + } + + if (newTasks.Count == 0) + { + Thread.Sleep(500); + } + else + { + newTasks.ToList().ForEach(x => tasks.Add(x.Key, x.Value)); + } + + numCompleted = checklist.Values.Sum(x => x ? 1 : 0); + Console.Write($"\rπŸ” Checked {numCompleted}/{checklist.Count} goals..."); + } + + Console.WriteLine(); + } + + succeededInv.ExceptWith(failedInv); + job.Output.WriteInfo($"\nπŸŽ‰ Verified {succeededInv.Count} invariants!"); + foreach (var inv in succeededInv) + { + if (!inv.IsDefault) + { + job.Output.WriteInfo($"βœ… {inv.Name.Replace("_PGROUP_", ": ")}"); + } + else + { + job.Output.WriteInfo($"βœ… default P proof obligations"); + } + + } + + MarkProvenInvariants(succeededInv); + ShowRemainings(job, failedInv, missingDefault); + if (failMessages.Count > 0) + { + job.Output.WriteInfo($"❌ Failed to verify {failMessages.Count} {(failMessages.Count > 1 ? "properties" : "property")}!"); + foreach (var msg in failMessages) + { + job.Output.WriteError(msg); + } + } + + db.Dispose(); + } + + private void MarkProvenInvariants(HashSet succeededInv) + { + foreach (var inv in _invariantDependencies.Keys) + { + _invariantDependencies[inv].ExceptWith(succeededInv); + } + + foreach (var inv in _invariantDependencies.Keys) + { + if (succeededInv.Contains(inv)) + { + _provenInvariants.Add(inv); + } + } + } + + private void ShowRemainings(ICompilerConfiguration job, HashSet failedInv, bool missingDefault) + { + HashSet remaining = []; + foreach (var inv in _provenInvariants) + { + foreach (var dep in _invariantDependencies[inv]) + { + if (!failedInv.Contains(dep) && !_provenInvariants.Contains(dep)) + { + remaining.Add(dep); + } + } + } + + if (remaining.Count > 0 || missingDefault) + { + job.Output.WriteWarning("❓ Remaining Goals:"); + foreach (var inv in remaining) + { + job.Output.WriteWarning($"- {inv.Name.Replace("_PGROUP_", ": ")} at {GetLocation(inv)}"); + } + + if (missingDefault) + { + job.Output.WriteWarning($"- default P proof obligations"); + } + } + } + + private void ProcessFailureMessages(List collection, string[] query, string reason, List failedInv, + List failMessages) + { + foreach (Match match in collection) + { + foreach (var feedback in match.Groups[1].Captures.Zip(match.Groups[2].Captures)) + { + var line = query[int.Parse(feedback.Second.ToString()) - 1]; + var step = feedback.First.ToString().Contains("[Step #0]") ? "(base case)" : ""; + var matchName = Regex.Match(line, @"// Failed to verify invariant (.*) at (.*)"); + if (matchName.Success) + { + var invName = matchName.Groups[1].Value.Replace("_PGROUP_", ": "); + failedInv.Add(invName); + failMessages.Add($"{reason} {line.Split("// ").Last()} {step}"); + } + + var matchDefault = Regex.Match(line, + @"(// Failed to verify that (.*) never receives (.*) in (.*)|// Failed to ensure unique action IDs at (.*)|// Failed to ensure increasing action IDs at (.*)|// Failed to ensure that received is a subset of sent at (.*))"); + if (matchDefault.Success) + { + failedInv.Add("default"); + failMessages.Add($"{reason} {line.Split("// ").Last()}"); + } + + var matchLoopInvs = Regex.Match(line, + @"// Failed to verify loop invariant at (.*)"); + if (matchLoopInvs.Success) + { + var msg = $"{reason} {line.Split("// ").Last()}"; + failMessages.Add(msg); + failedInv.Add("loop invariant @ " + matchLoopInvs.Groups[1].Value); + } + + var assertFails = Regex.Match(line, + @"// Failed to verify assertion at (.*)"); + if (assertFails.Success) + { + var parts = line.Split("// "); + var assertStmt = parts[0].Trim().Replace(";", ""); + var locInfo = assertFails.Groups[1].Value; + var msg = $"{reason} {assertStmt} at {locInfo} failed"; + failMessages.Add(msg); + failedInv.Add($"{assertStmt} @ " + locInfo); + } + } + } + } + + // returns invariants that were successfully verified and failure messages + private (HashSet, HashSet, List) AggregateResults(ICompilerConfiguration job, + string filename, List undefs, List fails) + { + var cmd = _fileToProofCommands[filename]; + var query = File.ReadLines(job.OutputDirectory.FullName + "/" + filename).ToArray(); + List failedInv = []; + List failMessages = []; + ProcessFailureMessages(fails, query, "❌", failedInv, failMessages); + ProcessFailureMessages(undefs, query, "❓", failedInv, failMessages); + if (failedInv.Count == 0) + { + return ([.. cmd.Goals], [], []); + } + + HashSet succeededInv = []; + foreach (var inv in cmd.Goals) + { + if (!failedInv.Contains(inv.Name)) + { + succeededInv.Add(inv); + } + } + + return (succeededInv, failedInv.Select(x => + { + if (x == "default") { + return _defaultInv; + } + _globalScope.Get(x, out Invariant i); + return i; + }).ToHashSet(), failMessages); + } + + public IEnumerable GenerateCode(ICompilerConfiguration job, Scope globalScope) + { + _ctx = new CompilationContext(job); + _optionsToDeclare = []; + _chooseToDeclare = []; + _specListenMap = new Dictionary>(); + _setCheckersToDeclare = []; + _fileToProofCommands = []; + _invariantDependencies = []; + _provenInvariants = []; + _proofCommandToFiles = []; + _commands = []; + _globalScope = globalScope; + BuildDependencies(globalScope); + PopulateSpecHandlers((from m in _globalScope.AllDecls.OfType() where m.IsSpec select m).ToList()); + var filenamePrefix = $"{job.ProjectName}_"; + List files = []; + // if there are no proof commands, then create two: + // - one called default for checking that everything is handled and sanity checkings + // - one with all the invariants as goals + if (!globalScope.ProofCommands.Any()) + { + var defaultProof = new ProofCommand("default", null) + { + Goals = [], + Premises = globalScope.AllDecls.OfType().ToList(), + }; + _commands.Add(defaultProof); + _proofCommandToFiles.Add(defaultProof, []); + files.AddRange(CompileToFile($"{filenamePrefix}default", defaultProof)); + + var fullProof = new ProofCommand("full", null) + { + Goals = globalScope.AllDecls.OfType().ToList(), + Premises = [], + }; + _commands.Add(fullProof); + _proofCommandToFiles.Add(fullProof, []); + files.AddRange(CompileToFile($"{filenamePrefix}full", fullProof)); + } + else + { + // otherwise, go through all the proof commands (or ones that are specified in the compiler config) + bool emitCode (ProofCommand cmd) => job.TargetProofBlocks.Count == 0 || + (cmd.ProofBlock != null && job.TargetProofBlocks.Contains(cmd.ProofBlock)); + foreach (var pbname in job.TargetProofBlocks) + { + if (!globalScope.Get(pbname, out ProofBlock pb)) + { + job.Output.WriteWarning($"Warning: proof block {pbname} not found. Skipping ..."); + } + } + foreach (var proofCmd in globalScope.ProofCommands.Where(emitCode)) + { + // if one of them is the default, rename it to default so that we can generate the init, next, and invs in compileToFile + // TODO: ensure that default can only happen on its own? + if (proofCmd.Goals.Count == 1 && proofCmd.Goals[0].IsDefault) + { + proofCmd.Name = "default"; + _defaultInv = proofCmd.Goals[0]; + _commands.Add(proofCmd); + _proofCommandToFiles.Add(proofCmd, []); + files.AddRange(CompileToFile($"{filenamePrefix}default", proofCmd)); + } + else + { + _proofCommandToFiles.Add(proofCmd, []); + files.AddRange(CompileToFile($"{filenamePrefix}{proofCmd.Name.Replace(",", "_").Replace(" ", "_")}", + proofCmd)); + _commands.Add(proofCmd); + } + } + } + + return files; + } + + private void PopulateSpecHandlers(IEnumerable specs) + { + foreach (var spec in specs) + { + var events = spec.Observes.Events; + foreach (var e in events) + { + var procedureName = $"{SpecPrefix}{spec.Name}_{e.Name}"; + if (_specListenMap.ContainsKey(e)) + { + _specListenMap[e].Add(procedureName); + } + else + { + _specListenMap.Add(e, [procedureName]); + } + } + } + } + + private CompiledFile GenerateCompiledFile(ProofCommand cmd, string name, Machine m, State s, Event e) + { + var filename = name; + + if (m != null) + { + filename += "_" + m.Name; + } + + if (s != null) + { + filename += "_" + s.Name; + } + + if (e != null) + { + filename += "_" + e.Name; + } + var md5 = MD5.Create(); + byte[] bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(filename)); + StringBuilder builder = new StringBuilder(); + foreach (byte b in bytes) { + builder.Append(b.ToString("x2")); // Convert to hexadecimal string + } + filename = builder + ".ucl"; + + var file = new CompiledFile(filename); + _fileToProofCommands.Add(filename, cmd); + _proofCommandToFiles[cmd].Add(filename); + return file; + } + + private List CompileToFile(string name, ProofCommand cmd) + { + // We essentially just want to call GenerateMain for every relevant bit of the proof command while filtering for checkOnly + var machines = (from m in _globalScope.AllDecls.OfType() where !m.IsSpec select m).ToList(); + var events = _globalScope.AllDecls.OfType().ToList(); + List files = []; + + if (cmd.Name == "default") + { + _src = GenerateCompiledFile(cmd, name, null, null, null); + files.Add(_src); + GenerateMain(null, null, null, cmd); + } + foreach (var m in machines) + { + if (_ctx.Job.CheckOnly == null || _ctx.Job.CheckOnly.Contains(m.Name)) + { + foreach (var s in m.States) + { + if (_ctx.Job.CheckOnly == null || _ctx.Job.CheckOnly.Contains(s.Name)) + { + _src = GenerateCompiledFile(cmd, name, m, s, null); + files.Add(_src); + GenerateMain(m, s, null, cmd, cmd.Name == "default"); + } + + foreach (var e in events.Where(e => !e.IsNullEvent && !e.IsHaltEvent && s.HasHandler(e))) + { + if (_ctx.Job.CheckOnly == null || _ctx.Job.CheckOnly.Contains(e.Name)) + { + _src = GenerateCompiledFile(cmd, name, m, s, e); + files.Add(_src); + GenerateMain(m, s, e, cmd, cmd.Name == "default"); + } + } + } + } + } + + Console.WriteLine($"Generated {files.Count} files for {name}"); + return files; + } + + private void BuildDependencies(Scope globalScope) + { + foreach (var cmd in globalScope.ProofCommands) + { + foreach (var goal in cmd.Goals) + { + foreach (var dep in cmd.Premises) + { + if (goal == dep) continue; + if (!_invariantDependencies.ContainsKey(goal)) + { + _invariantDependencies.Add(goal, []); + } + + _invariantDependencies[goal].Add(dep); + } + } + } + } + + private string ShowInvariant(IPExpr e) + { + if (e is InvariantRefExpr inv) return inv.Invariant.Name; + else return e.SourceLocation.GetText(); + } + + private void EmitLine(string str) + { + _ctx.WriteLine(_src.Stream, str); + } + + // Prefixes to avoid name clashes and keywords + private static string BuiltinPrefix => "P_"; + private static string UserPrefix => "User_"; + private static string EventPrefix => "Event_"; + private static string GotoPrefix => "PGoto_"; + private static string MachinePrefix => "PMachine_"; + private static string LocalPrefix => "PLocal_"; + private static string OptionPrefix => "POption_"; + private static string ChoosePrefix => "PChoose_"; + private static string CheckerPrefix => "PChecklist_"; + private static string SpecPrefix => "PSpec_"; + private static string InvariantPrefix => "PInv_"; + + // P values that don't have a direct UCLID5 equivalent + private static string PNull => $"{BuiltinPrefix}Null"; + private static string PNullDeclaration => $"type {PNull} = enum {{{PNull}}};"; + + private static string DefaultMachineRef => $"{BuiltinPrefix}DefaultMachine"; + private static string DefaultMachineDeclaration => $"const {DefaultMachineRef}: {MachineRefT};"; + + // P types that don't have a direct UCLID5 equivalent + private static string StringT => $"{BuiltinPrefix}String"; + private static string StringTDeclaration => $"type {StringT};"; + private static string DefaultString => $"{BuiltinPrefix}DefaultString"; + private static string DefaultStringDeclaration => $"const {DefaultString}: {StringT};"; + + /******************************** + * type StateADT = record {sent: [LabelADT]boolean, received: [LabelADT]boolean, machines: [MachineRefT]MachineStateADT}; + * var state: StateT; + *******************************/ + private static string StateAdt => $"{BuiltinPrefix}StateAdt"; + private static string StateAdtSentSelector => $"{StateAdt}_Sent"; + private static string StateAdtReceivedSelector => $"{StateAdt}_Received"; + private static string StateAdtMachinesSelector => $"{StateAdt}_Machines"; + + private static bool useLocalPrefix = false; + + private static string StateAdtConstruct(string sent, string received, string machines) + { + return + $"const_record({StateAdtSentSelector} := {sent}, {StateAdtReceivedSelector} := {received}, {StateAdtMachinesSelector} := {machines})"; + } + + private static string StateAdtSelectSent(string state) + { + return $"{state}.{StateAdtSentSelector}"; + } + + private static string StateAdtSelectReceived(string state) + { + return $"{state}.{StateAdtReceivedSelector}"; + } + + private static string StateAdtSelectMachines(string state) + { + return $"{state}.{StateAdtMachinesSelector}"; + } + + private static string StateAdtDeclaration() + { + return + $"type {StateAdt} = record {{{StateAdtSentSelector}: [{LabelAdt}]boolean, {StateAdtReceivedSelector}: [{LabelAdt}]boolean, {StateAdtMachinesSelector}: [{MachineRefT}]{MachineStateAdt}}};"; + } + + private static string InFlight(string state, string action) + { + return useLocalPrefix ? $"({LocalPrefix}sent[{action}] && !{StateAdtSelectReceived(state)}[{action}])" : $"({StateAdtSelectSent(state)}[{action}] && !{StateAdtSelectReceived(state)}[{action}])"; + } + + private static string StateVar => $"{BuiltinPrefix}State"; + private static string StateVarDeclaration => $"var {StateVar}: {StateAdt};"; + + private static string Deref(string r) + { + return $"{StateAdtSelectMachines(StateVar)}[{r}]"; + } + + /******************************** + * type MachineRef; + * + * type MachineStateADT = record {stage: boolean; machine: MachineADT}; + * + * // where Mi are the declared machines, Si their P state names, and MiFjl their fields + * type MachineADT = | M0(M0_State: S0, M0F00, ..., M0F0n) + * | ... + * | Mk(Mk_State: Sk, MkFk0, ..., MkFkm) + *******************************/ + private static string MachineRefT => $"{MachinePrefix}Ref_t"; + private static string MachineRefTDeclaration => $"type {MachineRefT};"; + private static string MachineStateAdt => $"{MachinePrefix}State_ADT"; + private static string MachineStateAdtStageSelector => $"{MachineStateAdt}_Stage"; + private static string MachineStateAdtMachineSelector => $"{MachineStateAdt}_Machine"; + + private static string MachineStateAdtConstruct(string stage, string machine) + { + return + $"const_record({MachineStateAdtStageSelector} := {stage}, {MachineStateAdtMachineSelector} := {machine})"; + } + + private static string MachineStateAdtSelectStage(string state) + { + return $"{state}.{MachineStateAdtStageSelector}"; + } + + private static string MachineStateAdtSelectMachine(string state) + { + return $"{state}.{MachineStateAdtMachineSelector}"; + } + + private static string MachineStateAdtDeclaration() + { + return + $"type {MachineStateAdt} = record {{{MachineStateAdtStageSelector}: boolean, {MachineStateAdtMachineSelector}: {MachineAdt}}};"; + } + + private static string MachineAdt => $"{MachinePrefix}ADT"; + + private string MachineAdtDeclaration(List machines) + { + var sum = string.Join("\n\t\t| ", machines.Select(ProcessMachine)); + return $"datatype {MachineAdt} = \n\t\t| {sum};"; + + string ProcessMachine(Machine m) + { + var fields = string.Join(", ", + m.Fields.Select(f => $"{MachinePrefix}{m.Name}_{f.Name}: {TypeToString(f.Type)}")); + if (m.Fields.Any()) fields = ", " + fields; + + return $"{MachinePrefix}{m.Name} ({MachinePrefix}{m.Name}_State: {MachinePrefix}{m.Name}_StateAdt{fields})"; + } + } + + private static string MachineAdtConstructM(Machine m, List args) + { + return $"{MachinePrefix}{m.Name}({string.Join(", ", args)})"; + } + + private static string MachineAdtSelectState(string instance, Machine m) + { + return $"{instance}.{MachinePrefix}{m.Name}_State"; + } + + private static string MachineStateAdtSelectState(string state, Machine m) + { + return MachineAdtSelectState(MachineStateAdtSelectMachine(state), m); + } + + private static string MachineAdtSelectField(string instance, Machine m, Variable f) + { + return $"{instance}.{MachinePrefix}{m.Name}_{f.Name}"; + } + + private static string MachineStateAdtSelectField(string state, Machine m, Variable f) + { + return MachineAdtSelectField(MachineStateAdtSelectMachine(state), m, f); + } + + private static string MachinePStateDeclaration(Machine m) + { + var states = string.Join(", ", m.States.Select(s => $"{MachinePrefix}{m.Name}_{s.Name}")); + return $"type {MachinePrefix}{m.Name}_StateAdt = enum {{{states}}};"; + } + + private static string MachineAdtIsM(string instance, Machine machine) + { + return $"is_{MachinePrefix}{machine.Name}({instance})"; + } + + private static string MachineStateAdtIsM(string state, Machine machine) + { + return $"is_{MachinePrefix}{machine.Name}({MachineStateAdtSelectMachine(state)})"; + } + + private static string MachineStateAdtInS(string state, Machine m, State s) + { + return + $"({MachineStateAdtIsM(state, m)} && {MachineStateAdtSelectState(state, m)} == {MachinePrefix}{m.Name}_{s.Name})"; + } + + + private static string InStartPredicateDeclaration(List machines) + { + var input = $"{LocalPrefix}state"; + var cases = machines.Select(ProcessMachine).ToList(); + var body = cases.Aggregate("false", (acc, pair) => $"if ({pair.Item1}) then ({pair.Item2})\n\t\telse ({acc})"); + return $"define {BuiltinPrefix}InStart({input}: {MachineStateAdt}) : boolean =\n\t\t{body};"; + + (string, string) ProcessMachine(Machine m) + { + var machine = $"{MachineStateAdtSelectMachine(input)}"; + var state = $"{machine}.{MachinePrefix}{m.Name}_State"; + var start = $"{MachinePrefix}{m.Name}_{m.StartState.Name}"; + var check = $"{state} == {start}"; + var guard = MachineAdtIsM(machine, m); + return (guard, check); + } + } + + private static string InEntryPredicateDeclaration() + { + var input = $"{LocalPrefix}state"; + return + $"define {BuiltinPrefix}InEntry({input}: {MachineStateAdt}) : boolean = {MachineStateAdtSelectStage(input)};"; + } + + /******************************** + * type LabelAdt = record {target: MachineRef, action: EitherEventOrGotoAdt} + * + * datatype EitherEventOrGotoAdt = | EventLabel (event: EventAdt) + * | GotoLabel (goto: GotoAdt) + * + * // where Ei is an event, Pi is the payload of the event and Ti is the type of the payload + * datatype EventAdt = | E0 (P0: T0) + * | ... + * | En (Pn: Tn) + * + * // where Si are as in MachineAdt and A0 are the arguments to the entry handler of the state + * datatype GotoAdt = | M0 (M0_State: S0, A0: T0) + * | ... + * | Mn (Mn_State: Sn, An: Tn) + *******************************/ + private static string LabelAdt => $"{BuiltinPrefix}Label"; + private static string LabelAdtTargetSelector => $"{LabelAdt}_Target"; + private static string LabelAdtActionSelector => $"{LabelAdt}_Action"; + private static string LabelAdtActionCountSelector => $"{LabelAdt}_ActionCount"; + + private static string LabelAdtDeclaration() + { + return + $"type {LabelAdt} = record {{{LabelAdtTargetSelector}: {MachineRefT}, {LabelAdtActionSelector}: {EventOrGotoAdt}, {LabelAdtActionCountSelector}: integer}};"; + } + + private void IncrementActionCount() + { + EmitLine($"{BuiltinPrefix}ActionCount = {BuiltinPrefix}ActionCount + 1;"); + } + + private static string LabelAdtConstruct(string target, string action) + { + return + $"const_record({LabelAdtTargetSelector} := {target}, {LabelAdtActionSelector} := {action}, {LabelAdtActionCountSelector} := {BuiltinPrefix}ActionCount)"; + } + + private static string LabelAdtSelectTarget(string label) + { + return $"{label}.{LabelAdtTargetSelector}"; + } + + private static string LabelAdtSelectAction(string label) + { + return $"{label}.{LabelAdtActionSelector}"; + } + + private static string LabelAdtSelectActionCount(string label) + { + return $"{label}.{LabelAdtActionCountSelector}"; + } + + + private static string EventOrGotoAdt => $"{BuiltinPrefix}EventOrGoto"; + private static string EventOrGotoAdtEventConstructor => $"{EventOrGotoAdt}_Event"; + private static string EventOrGotoAdtGotoConstructor => $"{EventOrGotoAdt}_Goto"; + private static string EventOrGotoAdtEventSelector => $"{EventOrGotoAdt}_Event_Event"; + private static string EventOrGotoAdtGotoSelector => $"{EventOrGotoAdt}_Goto_Goto"; + + private static string EventOrGotoAdtDeclaration() + { + var e = $"| {EventOrGotoAdtEventConstructor}({EventOrGotoAdtEventSelector}: {EventAdt})"; + var g = $"| {EventOrGotoAdtGotoConstructor}({EventOrGotoAdtGotoSelector}: {GotoAdt})"; + return $"datatype {EventOrGotoAdt} = \n\t\t{e}\n\t\t{g};"; + } + + private static string EventOrGotoAdtConstructEvent(string e) + { + return $"{EventOrGotoAdtEventConstructor}({e})"; + } + + private string EventOrGotoAdtConstructEvent(Event ev, IPExpr arg) + { + var payload = arg is null ? "" : ExprToString(arg); + var e = EventAdtConstruct(payload, ev); + return $"{EventOrGotoAdtEventConstructor}({e})"; + } + + + private static string EventOrGotoAdtConstructGoto(string g) + { + return $"{EventOrGotoAdtGotoConstructor}({g})"; + } + + private string EventOrGotoAdtConstructGoto(State state, IPExpr payload) + { + var g = GotoAdtConstruct(state, payload); + return $"{EventOrGotoAdtGotoConstructor}({g})"; + } + + private static string EventOrGotoAdtSelectEvent(string eventOrGoto) + { + return $"{eventOrGoto}.{EventOrGotoAdtEventSelector}"; + } + + private static string EventOrGotoAdtSelectGoto(string eventOrGoto) + { + return $"{eventOrGoto}.{EventOrGotoAdtGotoSelector}"; + } + + private static string EventOrGotoAdtIsEvent(string eventOrGoto) + { + return $"is_{EventOrGotoAdtEventConstructor}({eventOrGoto})"; + } + + private static string EventOrGotoAdtIsGoto(string eventOrGoto) + { + return $"is_{EventOrGotoAdtGotoConstructor}({eventOrGoto})"; + } + + private static string EventAdt => $"{EventPrefix}Adt"; + + private string EventAdtDeclaration(List events) + { + var declarationSum = string.Join("\n\t\t| ", events.Select(EventDeclarationCase)); + return $"datatype {EventAdt} = \n\t\t| {declarationSum};"; + + string EventDeclarationCase(Event e) + { + var pt = e.PayloadType.IsSameTypeAs(PrimitiveType.Null) + ? "" + : $"{EventPrefix}{e.Name}_Payload: {TypeToString(e.PayloadType)}"; + return + $"{EventPrefix}{e.Name} ({pt})"; + } + } + + private static string EventAdtSelectPayload(string eadt, Event e) + { + return $"{eadt}.{EventPrefix}{e.Name}_Payload"; + } + + private static string EventAdtConstruct(string payload, Event e) + { + return $"{EventPrefix}{e.Name}({payload})"; + } + + private static string EventAdtIsE(string instance, Event e) + { + return $"is_{EventPrefix}{e.Name}({instance})"; + } + + private static string EventOrGotoAdtIsE(string instance, Event e) + { + var isEvent = EventOrGotoAdtIsEvent(instance); + var selectEvent = EventOrGotoAdtSelectEvent(instance); + var correctEvent = EventAdtIsE(selectEvent, e); + return $"({isEvent} && {correctEvent})"; + } + + private static string LabelAdtIsE(string instance, Event e) + { + var action = LabelAdtSelectAction(instance); + return EventOrGotoAdtIsE(action, e); + } + + private static string LabelAdtSelectPayloadField(string instance, Event e, NamedTupleEntry field) + { + var action = LabelAdtSelectAction(instance); + return $"{EventAdtSelectPayload(EventOrGotoAdtSelectEvent(action), e)}.{field.Name}"; + } + + private static string GotoAdt => $"{GotoPrefix}Adt"; + + private string GotoAdtDeclaration(IEnumerable machines) + { + List<(State, Variable)> gotos = []; + foreach (var m in machines) + foreach (var s in m.States) + { + var f = s.Entry; + // get the arguments to the entry handler + Variable a = null; + if (f is not null && s.Entry.Signature.Parameters.Count > 0) + { + a = s.Entry.Signature.Parameters[0]; + } + + gotos.Add((s, a)); + } + + var sum = string.Join("\n\t\t| ", gotos.Select(ProcessGoto)); + + return $"datatype {GotoAdt} = \n\t\t| {sum};"; + + string ProcessGoto((State, Variable) g) + { + var prefix = $"{GotoPrefix}{g.Item1.OwningMachine.Name}_{g.Item1.Name}"; + if (g.Item2 is null) + { + return prefix + "()"; + } + + return prefix + $"({prefix}_{g.Item2.Name}: {TypeToString(g.Item2.Type)})"; + } + } + + private string GotoAdtConstruct(State s, IPExpr p) + { + var payload = p is null ? "" : ExprToString(p); + return $"{GotoPrefix}{s.OwningMachine.Name}_{s.Name}({payload})"; + } + + private string GotoAdtSelectParam(string instance, string param, State s) + { + return $"{instance}.{GotoPrefix}{s.OwningMachine.Name}_{s.Name}_{param}"; + } + + private static string GotoAdtIsS(string instance, State s) + { + return $"is_{GotoPrefix}{s.OwningMachine.Name}_{s.Name}({instance})"; + } + + private static string EventOrGotoAdtIsS(string instance, State s) + { + var isGoto = EventOrGotoAdtIsGoto(instance); + var selectGoto = EventOrGotoAdtSelectGoto(instance); + var correctGoto = GotoAdtIsS(selectGoto, s); + return $"({isGoto} && {correctGoto})"; + } + + private static string LabelAdtIsS(string instance, State s) + { + var action = LabelAdtSelectAction(instance); + return EventOrGotoAdtIsS(action, s); + } + + + /******************************** + * Spec machines are treated differently than regular machines. For example, there is only ever a single instance of + * each spec machine. + * + * To handle spec machines, + * 1) we create a global variable for each of their fields; + * 2) keep track of what events the spec machines are listening to and the corresponding handleers; + * 3) create a procedure per spec machine handler that operates on the variables from (1); and + * 4) whenever a regular machine sends an event, we call the appropriate handler from (2). + *******************************/ + private void SpecVariableDeclarations(List specs) + { + foreach (var spec in specs) + { + EmitLine( + $"type {SpecPrefix}{spec.Name}_StateAdt = enum {{{string.Join(", ", spec.States.Select(n => $"{SpecPrefix}{spec.Name}_{n.Name}"))}}};"); + EmitLine($"var {SpecPrefix}{spec.Name}_State: {SpecPrefix}{spec.Name}_StateAdt;"); + foreach (var f in spec.Fields) + { + EmitLine($"var {SpecPrefix}{spec.Name}_{f.Name}: {TypeToString(f.Type)};"); + } + } + } + + private void GenerateSpecProcedures(List specs, List goals, bool generateSanityChecks = false) + { + foreach (var spec in specs) + { + foreach (var f in spec.Methods) + { + var ps = f.Signature.Parameters.Select(p => $"{GetLocalName(p)}: {TypeToString(p.Type)}"); + var line = _ctx.LocationResolver.GetLocation(f.SourceLocation).Line; + var name = f.Name == "" ? $"{BuiltinPrefix}{f.Owner.Name}_f{line}" : f.Name; + EmitLine($"procedure [inline] {name}({string.Join(", ", ps)})"); + if (!f.Signature.ReturnType.IsSameTypeAs(PrimitiveType.Null)) + { + EmitLine($"\treturns ({BuiltinPrefix}Return: {TypeToString(f.Signature.ReturnType)})"); + } + + EmitLine("{"); + + // declare local variables corresponding to the global spec variables + EmitLine($"var {LocalPrefix}state: {SpecPrefix}{spec.Name}_StateAdt;"); + EmitLine($"var {LocalPrefix}sent: [{LabelAdt}]boolean;"); + foreach (var v in spec.Fields) + { + EmitLine($"var {GetLocalName(v)}: {TypeToString(v.Type)};"); + } + + // declare local variables for the method + foreach (var v in f.LocalVariables) EmitLine($"var {GetLocalName(v)}: {TypeToString(v.Type)};"); + // foreach (var v in f.LocalVariables) EmitLine($"{GetLocalName(v)} = {DefaultValue(v.Type)};"); + + // Set the local variables corresponding to the global spec variables to the correct starting value + EmitLine($"{LocalPrefix}sent = {StateAdtSelectSent(StateVar)};"); + foreach (var v in spec.Fields) + EmitLine($"{GetLocalName(v)} = {SpecPrefix}{spec.Name}_{v.Name};"); + + useLocalPrefix = true; + GenerateStmt(f.Body, spec, goals, generateSanityChecks); + useLocalPrefix = false; + + // update the global variables + EmitLine($"{SpecPrefix}{spec.Name}_State = {LocalPrefix}state;"); + foreach (var v in spec.Fields) + { + EmitLine($"{SpecPrefix}{spec.Name}_{v.Name} = {GetLocalName(v)};"); + } + + EmitLine("}\n"); + } + } + } + + private void GenerateSpecHandlers(List specs) + { + foreach (var spec in specs) + { + var events = spec.Observes.Events; + foreach (var e in events) + { + var procedureName = $"{SpecPrefix}{spec.Name}_{e.Name}"; + Trace.Assert(_specListenMap.ContainsKey(e) && _specListenMap[e].Contains(procedureName), + $"Procedure {procedureName} is not generated for Spec {spec.Name} that listens to {e.Name}"); + + EmitLine($"procedure [inline] {procedureName}({SpecPrefix}Payload: {TypeToString(e.PayloadType)})"); + EmitLine("{"); + EmitLine("case"); + foreach (var state in spec.States) + { + var handlers = state.AllEventHandlers.ToDictionary(); + if (state.HasHandler(e)) + { + var handler = handlers[e]; + var precondition = $"{SpecPrefix}{spec.Name}_State == {SpecPrefix}{spec.Name}_{state.Name}"; + EmitLine($"({precondition}) : {{"); + switch (handler) + { + case EventDoAction eventDoAction: + var f = eventDoAction.Target; + var line = _ctx.LocationResolver.GetLocation(f.SourceLocation).Line; + var name = f.Name == "" ? $"{BuiltinPrefix}{f.Owner.Name}_f{line}" : f.Name; + var payload = f.Signature.Parameters.Count > 0 ? $"{SpecPrefix}Payload" : ""; + EmitLine($"call {name}({payload});"); + break; + default: + throw new NotSupportedException( + $"Not supported handler ({handler}) at {GetLocation(handler)}"); + } + + EmitLine("}"); + } + } + + EmitLine("esac"); + EmitLine("}"); + } + } + } + + /******************************** + * Traverse the P AST and generate the UCLID5 code using the types and helpers defined above + *******************************/ + private void GenerateMain(Machine machine, State state, Event @event, ProofCommand cmd, bool generateSanityChecks = false) + { + EmitLine("module main {"); + + var machines = (from m in _globalScope.AllDecls.OfType() where !m.IsSpec select m).ToList(); + var specs = (from m in _globalScope.AllDecls.OfType() where m.IsSpec select m).ToList(); + var events = _globalScope.AllDecls.OfType().ToList(); + var axioms = _globalScope.AllDecls.OfType().ToList(); + var pures = _globalScope.AllDecls.OfType().ToList(); + + EmitLine(PNullDeclaration); + EmitLine(DefaultMachineDeclaration); + EmitLine(StringTDeclaration); + EmitLine(DefaultStringDeclaration); + EmitLine(""); + + GenerateUserEnums(_globalScope.AllDecls.OfType()); + GenerateUserTypes(_globalScope.AllDecls.OfType()); + EmitLine(""); + + EmitLine(EventAdtDeclaration(events)); + EmitLine(""); + EmitLine(GotoAdtDeclaration(machines)); + EmitLine(""); + EmitLine(EventOrGotoAdtDeclaration()); + EmitLine(LabelAdtDeclaration()); + EmitLine($"var {BuiltinPrefix}ActionCount: integer;"); + EmitLine(""); + + EmitLine(MachineRefTDeclaration); + foreach (var m in machines) + { + EmitLine(MachinePStateDeclaration(m)); + } + + EmitLine(MachineAdtDeclaration(machines)); + EmitLine(MachineStateAdtDeclaration()); + EmitLine(""); + + EmitLine(StateAdtDeclaration()); + EmitLine(StateVarDeclaration); + EmitLine(""); + + SpecVariableDeclarations(specs); + EmitLine(""); + + EmitLine(InStartPredicateDeclaration(machines)); + EmitLine(InEntryPredicateDeclaration()); + EmitLine(""); + + // global functions + GenerateGlobalProcedures(_globalScope.AllDecls.OfType(), cmd.Goals, generateSanityChecks); + + // Spec support + GenerateSpecProcedures(specs, cmd.Goals, generateSanityChecks); // generate spec methods, called by spec handlers + GenerateSpecHandlers(specs); // will populate _specListenMap, which is used when ever there is a send statement + EmitLine(""); + + foreach (var pure in pures) + { + var args = string.Join(", ", + pure.Signature.Parameters.Select(p => $"{LocalPrefix}{p.Name}: {TypeToString(p.Type)}")); + if (pure.Body is null) + { + EmitLine($"function {pure.Name}({args}): {TypeToString(pure.Signature.ReturnType)};"); + } + else + { + EmitLine( + $"define {pure.Name}({args}): {TypeToString(pure.Signature.ReturnType)} = {ExprToString(pure.Body)};"); + } + } + + EmitLine(""); + + foreach (var inv in cmd.Premises) + { + EmitLine($"define {InvariantPrefix}{inv.Name}(): boolean = {ExprToString(inv.Body)};"); + } + + foreach (var inv in cmd.Goals) + { + if (!inv.IsDefault) { + EmitLine($"define {InvariantPrefix}{inv.Name}(): boolean = {ExprToString(inv.Body)};"); + } + } + + EmitLine(""); + + foreach (var ax in axioms) + { + EmitLine($"axiom {ExprToString(ax.Body)};"); + } + + EmitLine(""); + + // invariants to ensure unique action IDs + EmitLine( + $"define {InvariantPrefix}Unique_Actions(): boolean = forall (a1: {LabelAdt}, a2: {LabelAdt}) :: (a1 != a2 && {StateAdtSelectSent(StateVar)}[a1] && {StateAdtSelectSent(StateVar)}[a2]) ==> {LabelAdtSelectActionCount("a1")} != {LabelAdtSelectActionCount("a2")};"); + EmitLine($"invariant _{InvariantPrefix}Unique_Actions: {InvariantPrefix}Unique_Actions();"); + EmitLine( + $"define {InvariantPrefix}Increasing_Action_Count(): boolean = forall (a: {LabelAdt}) :: {StateAdtSelectSent(StateVar)}[a] ==> {LabelAdtSelectActionCount("a")} < {BuiltinPrefix}ActionCount;"); + EmitLine( + $"invariant _{InvariantPrefix}Increasing_Action_Count: {InvariantPrefix}Increasing_Action_Count();"); + // invariants to ensure received is a subset of sent + EmitLine( + $"define {InvariantPrefix}Received_Subset_Sent(): boolean = forall (a: {LabelAdt}) :: {StateAdtSelectReceived(StateVar)}[a] ==> {StateAdtSelectSent(StateVar)}[a];"); + EmitLine($"invariant _{InvariantPrefix}Received_Subset_Sent: {InvariantPrefix}Received_Subset_Sent();"); + EmitLine(""); + GenerateOptionTypes(); + EmitLine(""); + + if (cmd.Name == "default" && machine == null && state == null && @event == null) + { + GenerateInitBlock(machines, specs, _globalScope.AllDecls.OfType()); + EmitLine(""); + GenerateNextBlock(machines, events); + EmitLine(""); + + foreach (var inv in cmd.Premises) + { + EmitLine($"invariant _{InvariantPrefix}{inv.Name}: {InvariantPrefix}{inv.Name}();"); + } + + EmitLine(""); + GenerateControlBlock(null, null, null, true); + } + else + { + // non-handler functions for handlers + GenerateMachineProcedures(machine, cmd.Goals, generateSanityChecks); // generate machine methods, called by handlers below + EmitLine(""); + + // generate the handlers + if (@event != null) + { + GenerateEventHandler(state, @event, cmd.Goals, cmd.Premises, generateSanityChecks); + } + else + { + GenerateEntryHandler(state, cmd.Goals, cmd.Premises, generateSanityChecks); + } + + EmitLine(""); + + // These have to be done at the end because we don't know what we need until we generate the rest of the code + EmitLine(""); + GenerateCheckerVars(); + EmitLine(""); + GenerateChooseProcedures(); + EmitLine(""); + + GenerateControlBlock(machine, state, @event, false); + } + + // close the main module + EmitLine("}"); + } + + private void GenerateUserEnums(IEnumerable enums) + { + foreach (var e in enums) + { + var variants = string.Join(", ", e.Values.Select(v => UserPrefix + v.Name)); + EmitLine($"type {UserPrefix}{e.Name} = enum {{{variants}}};"); + } + } + + private void GenerateUserTypes(IEnumerable types) + { + foreach (var t in types) EmitLine($"type {UserPrefix}{t.Name} = {TypeToString(t.Type)};"); + } + + private void GenerateInitBlock(List machines, List specs, IEnumerable starts) + { + var state = Deref("r"); + EmitLine("init {"); + EmitLine("// Every machine begins in their start state"); + EmitLine($"assume(forall (r: {MachineRefT}) :: {BuiltinPrefix}InStart({state}));"); + EmitLine("// Every machine begins with their entry flag set"); + EmitLine($"assume(forall (r: {MachineRefT}) :: {BuiltinPrefix}InEntry({state}));"); + EmitLine("// The buffer starts completely empty"); + EmitLine($"{StateAdtSelectSent(StateVar)} = const(false, [{LabelAdt}]boolean);"); + EmitLine($"{StateAdtSelectReceived(StateVar)} = const(false, [{LabelAdt}]boolean);"); + + EmitLine("// User assumptions"); + foreach (var assumes in starts) + { + EmitLine($"assume ({ExprToString(assumes.Body)}); // {assumes.Name}"); + } + + // close the init block + EmitLine("}"); + } + + private void GenerateNextBlock(List machines, List events) + { + var currentLabel = $"{BuiltinPrefix}CurrentLabel"; + // pick a random label and handle it + EmitLine("next {"); + EmitLine($"var {currentLabel}: {LabelAdt};"); + EmitLine($"if ({InFlight(StateVar, currentLabel)}) {{"); + EmitLine("case"); + foreach (var m in machines) + { + foreach (var s in m.States) + { + foreach (var e in events.Where(e => !e.IsNullEvent && !e.IsHaltEvent)) + { + if (!s.HasHandler(e)) + { + if (_ctx.Job.CheckOnly is null || _ctx.Job.CheckOnly == m.Name || + _ctx.Job.CheckOnly == s.Name || _ctx.Job.CheckOnly == e.Name) + { + EmitLine($"({EventGuard(m, s, e)}) : {{"); + EmitLine( + $"assert false; // Failed to verify that {m.Name} never receives {e.Name} in {s.Name}"); + EmitLine("}"); + } + } + } + } + } + + EmitLine("esac"); + EmitLine("}"); + // close the next block + EmitLine("}"); + return; + + string EventGuard(Machine m, State s, Event e) + { + var correctMachine = MachineStateAdtIsM(Deref(LabelAdtSelectTarget(currentLabel)), m); + var correctState = MachineStateAdtInS(Deref(LabelAdtSelectTarget(currentLabel)), m, s); + var correctEvent = LabelAdtIsE(currentLabel, e); + return string.Join(" && ", [correctMachine, correctState, correctEvent]); + } + } + + private void GenerateGlobalProcedures(IEnumerable functions, List goals, + bool generateSanityChecks = false) + { + // TODO: these should be side-effect free and we should enforce that + foreach (var f in functions) + { + var ps = f.Signature.Parameters.Select(p => $"{GetLocalName(p)}: {TypeToString(p.Type)}") + .Prepend($"this: {MachineRefT}"); + + if (f.Body is null) + { + EmitLine($"procedure [noinline] {f.Name}({string.Join(", ", ps)})"); + } + else + { + EmitLine($"procedure [inline] {f.Name}({string.Join(", ", ps)})"); + } + + if (!f.Signature.ReturnType.IsSameTypeAs(PrimitiveType.Null)) + { + var retName = f.ReturnVariable is null + ? $"{BuiltinPrefix}Return" + : $"{LocalPrefix}{f.ReturnVariable.Name}"; + EmitLine($"\treturns ({retName}: {TypeToString(f.Signature.ReturnType)})"); + } + + var i = 0; + foreach (var req in f.Requires) + { + i += 1; + EmitLine($"requires {ExprToString(req)}; // Violated precondition #{i} of {f.Name}"); + } + + foreach (var ensure in f.Ensures) + { + EmitLine($"ensures {ExprToString(ensure)};"); + } + + EmitLine("{"); + // declare local variables for the method + foreach (var v in f.LocalVariables) EmitLine($"var {GetLocalName(v)}: {TypeToString(v.Type)};"); + GenerateStmt(f.Body, null, goals, generateSanityChecks); + EmitLine("}\n"); + } + } + + private void GenerateMachineProcedures(Machine m, List goals, bool generateSanityChecks = false) + { + foreach (var f in m.Methods) + { + var ps = f.Signature.Parameters.Select(p => $"{GetLocalName(p)}: {TypeToString(p.Type)}") + .Prepend($"this: {MachineRefT}"); + var line = _ctx.LocationResolver.GetLocation(f.SourceLocation).Line; + var name = f.Name == "" ? $"{BuiltinPrefix}{f.Owner.Name}_f{line}" : f.Name; + EmitLine($"procedure [inline] {name}({string.Join(", ", ps)})"); + + var currState = Deref("this"); + + if (!f.Signature.ReturnType.IsSameTypeAs(PrimitiveType.Null)) + { + EmitLine($"\treturns ({BuiltinPrefix}Return: {TypeToString(f.Signature.ReturnType)})"); + } + + EmitLine("{"); + + // declare necessary local variables + EmitLine($"var {LocalPrefix}state: {MachinePrefix}{m.Name}_StateAdt;"); + EmitLine($"var {LocalPrefix}stage: boolean;"); + EmitLine($"var {LocalPrefix}sent: [{LabelAdt}]boolean;"); + foreach (var v in m.Fields) EmitLine($"var {GetLocalName(v)}: {TypeToString(v.Type)};"); + foreach (var v in f.LocalVariables) EmitLine($"var {GetLocalName(v)}: {TypeToString(v.Type)};"); + + // initialize all the local variables to the correct values + EmitLine($"{LocalPrefix}state = {MachineStateAdtSelectState(currState, m)};"); + EmitLine($"{LocalPrefix}stage = false;"); // this can be set to true by a goto statement + EmitLine($"{LocalPrefix}sent = {StateAdtSelectSent(StateVar)};"); + foreach (var v in m.Fields) + EmitLine($"{GetLocalName(v)} = {MachineStateAdtSelectField(currState, m, v)};"); + // foreach (var v in f.LocalVariables) EmitLine($"{GetLocalName(v)} = {DefaultValue(v.Type)};"); + + useLocalPrefix = true; + GenerateStmt(f.Body, null, goals, generateSanityChecks); + useLocalPrefix = false; + + var fields = m.Fields.Select(GetLocalName).Prepend($"{LocalPrefix}state").ToList(); + + // make a new machine + var newMachine = MachineAdtConstructM(m, fields); + // make a new machine state + var newMachineState = MachineStateAdtConstruct($"{LocalPrefix}stage", newMachine); + // update the machine map + EmitLine( + $"{StateAdtSelectMachines(StateVar)} = {StateAdtSelectMachines(StateVar)}[this -> {newMachineState}];"); + // update the buffer + EmitLine($"{StateAdtSelectSent(StateVar)} = {LocalPrefix}sent;"); + + EmitLine("}\n"); + } + } + + private void GenerateEntryHandler(State s, List goals, List requires, + bool generateSanityChecks = false) + { + var label = $"{LocalPrefix}Label"; + var target = LabelAdtSelectTarget(label); + var targetMachineState = Deref(target); + var action = LabelAdtSelectAction(label); + var g = EventOrGotoAdtSelectGoto(action); + var received = StateAdtSelectReceived(StateVar); + + EmitLine($"procedure [noinline] {s.OwningMachine.Name}_{s.Name}({label}: {LabelAdt})"); + EmitLine($"\trequires {InFlight(StateVar, label)};"); + EmitLine($"\trequires {EventOrGotoAdtIsGoto(action)};"); + EmitLine($"\trequires {GotoAdtIsS(g, s)};"); + EmitLine($"\trequires {MachineStateAdtInS(targetMachineState, s.OwningMachine, s)};"); + + EmitLine($"\trequires {InvariantPrefix}Unique_Actions();"); + EmitLine($"\trequires {InvariantPrefix}Increasing_Action_Count();"); + EmitLine($"\trequires {InvariantPrefix}Received_Subset_Sent();"); + + if (generateSanityChecks) + { + EmitLine($"\tensures {InvariantPrefix}Unique_Actions(); // Failed to ensure unique action IDs at {GetLocation(s)}"); + EmitLine($"\tensures {InvariantPrefix}Increasing_Action_Count(); // Failed to ensure increasing action IDs at {GetLocation(s)}"); + EmitLine($"\tensures {InvariantPrefix}Received_Subset_Sent(); // Failed to ensure that received is a subset of sent at {GetLocation(s)}"); + } + + foreach (var reqs in requires) + { + EmitLine($"\trequires {InvariantPrefix}{reqs.Name}();"); + } + + foreach (var inv in goals) + { + if (!inv.IsDefault) { + EmitLine($"\trequires {InvariantPrefix}{inv.Name}();"); + EmitLine( + $"\tensures {InvariantPrefix}{inv.Name}(); // Failed to verify invariant {inv.Name.Replace("_PGROUP_", ": ")} at {GetLocation(s)}"); + } + } + + EmitLine("{"); + + if (s.Entry is null) + { + EmitLine($"var {LocalPrefix}stage: boolean;"); + EmitLine($"var {LocalPrefix}state: {MachinePrefix}{s.OwningMachine.Name}_StateAdt;"); + EmitLine($"var {LocalPrefix}received: [{LabelAdt}]boolean;"); + + EmitLine($"{LocalPrefix}stage = false;"); + EmitLine($"{LocalPrefix}state = {MachineStateAdtSelectState(targetMachineState, s.OwningMachine)};"); + EmitLine($"{received} = {received}[{label} -> true];"); + EmitLine($"{LocalPrefix}received = {received};"); + + var fields = s.OwningMachine.Fields + .Select(v => MachineStateAdtSelectField(targetMachineState, s.OwningMachine, v)) + .Prepend($"{LocalPrefix}state").ToList(); + // make a new machine + var newMachine = MachineAdtConstructM(s.OwningMachine, fields); + // make a new machine state + var newMachineState = MachineStateAdtConstruct($"{LocalPrefix}stage", newMachine); + // update the machine map + EmitLine( + $"{StateAdtSelectMachines(StateVar)} = {StateAdtSelectMachines(StateVar)}[{target} -> {newMachineState}];"); + // update the buffer + EmitLine($"{received} = {LocalPrefix}received;"); + } + else + { + var f = s.Entry; + var line = _ctx.LocationResolver.GetLocation(f.SourceLocation).Line; + var name = f.Name == "" ? $"{BuiltinPrefix}{f.Owner.Name}_f{line}" : f.Name; + var payload = f.Signature.Parameters.Count > 0 + ? $", {GotoAdtSelectParam(g, f.Signature.Parameters[0].Name, s)}" + : ""; + EmitLine($"{received} = {received}[{label} -> true];"); + EmitLine($"call {name}({target}{payload});"); + } + + foreach (var reqs in requires) + { + EmitLine($"assume {InvariantPrefix}{reqs.Name}();"); + } + + EmitLine("}\n"); + } + + + private void GenerateEventHandler(State s, Event ev, List goals, List requires, + bool generateSanityChecks = false) + { + var label = $"{LocalPrefix}Label"; + EmitLine($"procedure [noinline] {s.OwningMachine.Name}_{s.Name}_{ev.Name}({label}: {LabelAdt})"); + + var target = LabelAdtSelectTarget(label); + var targetMachineState = Deref(target); + var action = LabelAdtSelectAction(label); + var e = EventOrGotoAdtSelectEvent(action); + var received = StateAdtSelectReceived(StateVar); + + EmitLine($"\trequires {InFlight(StateVar, label)};"); + EmitLine($"\trequires {MachineStateAdtInS(targetMachineState, s.OwningMachine, s)};"); + EmitLine($"\trequires {EventOrGotoAdtIsEvent(action)};"); + EmitLine($"\trequires {EventAdtIsE(e, ev)};"); + + EmitLine($"\trequires {InvariantPrefix}Unique_Actions();"); + EmitLine($"\trequires {InvariantPrefix}Increasing_Action_Count();"); + EmitLine($"\trequires {InvariantPrefix}Received_Subset_Sent();"); + + var handler = s.AllEventHandlers.ToDictionary()[ev]; + + if (generateSanityChecks) + { + EmitLine($"\tensures {InvariantPrefix}Unique_Actions(); // Failed to ensure unique action IDs at {GetLocation(handler)}"); + EmitLine($"\tensures {InvariantPrefix}Increasing_Action_Count(); // Failed to ensure increasing action IDs at {GetLocation(handler)}"); + EmitLine($"\tensures {InvariantPrefix}Received_Subset_Sent(); // Failed to ensure that received is a subset of sent at {GetLocation(handler)}"); + } + + foreach (var reqs in requires) + { + EmitLine($"\trequires {InvariantPrefix}{reqs.Name}();"); + } + + foreach (var inv in goals) + { + if (!inv.IsDefault) { + EmitLine($"\trequires {InvariantPrefix}{inv.Name}();"); + EmitLine( + $"\tensures {InvariantPrefix}{inv.Name}(); // Failed to verify invariant {inv.Name.Replace("_PGROUP_", ": ")} at {GetLocation(handler)}"); + } + } + + switch (handler) + { + case EventDefer _: + EmitLine("{"); + EmitLine("}\n"); + return; + case EventDoAction eventDoAction: + var f = eventDoAction.Target; + var line = _ctx.LocationResolver.GetLocation(f.SourceLocation).Line; + var name = f.Name == "" ? $"{BuiltinPrefix}{f.Owner.Name}_f{line}" : f.Name; + var payload = f.Signature.Parameters.Count > 0 ? $", {EventAdtSelectPayload(e, ev)}" : ""; + EmitLine("{"); + EmitLine($"call {name}({target}{payload});"); + EmitLine($"{received} = {received}[{label} -> true];"); + foreach (var reqs in requires) + { + EmitLine($"assume {InvariantPrefix}{reqs.Name}();"); + } + EmitLine("}\n"); + return; + case EventIgnore _: + EmitLine("{"); + EmitLine("}\n"); + return; + default: + throw new NotSupportedException($"Not supported handler ({handler}) at {GetLocation(handler)}"); + } + } + + + private void GenerateControlBlock(Machine m, State s, Event e, Boolean handlerCheck) + { + // handlerCheck iff all the others are null + Trace.Assert(handlerCheck == (m is null && s is null && e is null)); + + EmitLine("control {"); + EmitLine($"set_solver_option(\":Timeout\", {_ctx.Job.Timeout});"); // timeout per query in seconds + + if (handlerCheck) + { + EmitLine("induction(1);"); + } + else + { + if (e == null) + { + EmitLine($"verify({m.Name}_{s.Name});"); + } + else + { + EmitLine($"verify({m.Name}_{s.Name}_{e.Name});"); + } + } + + EmitLine("check;"); + EmitLine("print_results;"); + EmitLine("}"); + } + + private string DefaultValue(PLanguageType ty) + { + return ty switch + { + EnumType enumType => UserPrefix + enumType.EnumDecl.Values.First().Name, + MapType mapType => + $"const({GetOptionName(mapType.ValueType)}_None(), {TypeToString(mapType)})", + NamedTupleType ntt => + $"const_record({string.Join(", ", ntt.Fields.Select(f => $"{f.Name} := {DefaultValue(f.Type)}"))})", + PermissionType _ => DefaultMachineRef, + PrimitiveType pt when pt.Equals(PrimitiveType.Bool) => "false", + PrimitiveType pt when pt.Equals(PrimitiveType.Int) => "0", + PrimitiveType pt when pt.Equals(PrimitiveType.String) => DefaultString, + PrimitiveType pt when pt.Equals(PrimitiveType.Machine) => DefaultMachineRef, + SetType setType => $"const(false, {TypeToString(setType)})", + TypeDefType tdType => DefaultValue(tdType.TypeDefDecl.Type), + _ => throw new NotSupportedException($"Not supported default: {ty} ({ty.OriginalRepresentation})"), + }; + } + + private void GenerateOptionTypes() + { + foreach (var ptype in _optionsToDeclare) + { + var opt = GetOptionName(ptype); + EmitLine($"datatype {opt} = "); + EmitLine($"\t| {opt}_Some ({opt}_Some_Value: {TypeToString(ptype)})"); + EmitLine($"\t| {opt}_None ();"); + } + } + + private void GenerateChooseProcedures() + { + foreach (var choosePt in _chooseToDeclare) + { + var proc = GetChooseName(choosePt); + EmitLine($"procedure [noinline] {proc}(inp: {TypeToString(choosePt)})"); + switch (choosePt) + { + case MapType mapType: + EmitLine($"\treturns (outp: {TypeToString(mapType.KeyType)})"); + EmitLine($"\tensures {OptionIsSome(mapType.ValueType, "inp[outp]")};"); + break; + case SetType setType: + EmitLine($"\treturns (outp: {TypeToString(setType.ElementType)})"); + EmitLine($"\tensures inp[outp];"); + break; + case PrimitiveType pt when pt.Equals(PrimitiveType.Int): + EmitLine($"\treturns (outp: {TypeToString(pt)})"); + EmitLine($"\tensures 0 <= outp && outp <= inp;"); + break; + default: + throw new NotSupportedException( + $"Not supported choose type: {choosePt} ({choosePt.OriginalRepresentation})"); + } + + EmitLine("{"); + EmitLine("}\n"); + } + } + + private string OptionConstructSome(PLanguageType t, string value) + { + return $"{GetOptionName(t)}_Some({value})"; + } + + private string OptionConstructNone(PLanguageType t) + { + return $"{GetOptionName(t)}_None()"; + } + + private string OptionIsSome(PLanguageType t, string instance) + { + return $"is_{GetOptionName(t)}_Some({instance})"; + } + + private string OptionIsNone(PLanguageType t, string instance) + { + return $"is_{GetOptionName(t)}_None({instance})"; + } + + private string OptionSelectValue(PLanguageType t, string instance) + { + return $"{instance}.{GetOptionName(t)}_Some_Value"; + } + + private void GenerateCheckerVars() + { + foreach (var ptype in _setCheckersToDeclare) + { + var name = GetCheckerName(ptype); + EmitLine($"var {name}: {TypeToString(ptype)};"); + } + } + + // specMachine is null iff we are not generating a statement for a spec machine + private void GenerateStmt(IPStmt stmt, Machine specMachine, List goals, + bool generateSanityChecks = false) + { + switch (stmt) + { + case CompoundStmt cstmt: + foreach (var s in cstmt.Statements) GenerateStmt(s, specMachine, goals, generateSanityChecks); + return; + case AssignStmt { Value: FunCallExpr, Location: VariableAccessExpr } cstmt: + var call = cstmt.Value as FunCallExpr; + var avax = cstmt.Location as VariableAccessExpr; + if (call == null) return; + var v = ExprToString(avax); + var f = call.Function.Name; + var fargs = call.Arguments.Select(ExprToString); + if (call.Function.Owner is not null) + { + fargs = fargs.Prepend("this"); + } + + EmitLine($"call ({v}) = {f}({string.Join(", ", fargs.Prepend("this"))});"); + return; + case AssignStmt { Value: ChooseExpr, Location: VariableAccessExpr } cstmt: + var chooseExpr = (ChooseExpr)cstmt.Value; + _chooseToDeclare.Add(chooseExpr.SubExpr.Type); + var cvax = (VariableAccessExpr)cstmt.Location; + var cv = ExprToString(cvax); + var cf = GetChooseName(chooseExpr.SubExpr.Type); + var arg = ExprToString(chooseExpr.SubExpr); + EmitLine($"call ({cv}) = {cf}({arg});"); + return; + case AssignStmt astmt: + switch (astmt.Location) + { + case VariableAccessExpr vax: + EmitLine($"{ExprToString(vax)} = {ExprToString(astmt.Value)};"); + return; + case MapAccessExpr { MapExpr: VariableAccessExpr } max: + var map = ExprToString(max.MapExpr); + var index = ExprToString(max.IndexExpr); + var t = ((MapType)max.MapExpr.Type).ValueType; + EmitLine($"{map} = {map}[{index} -> {OptionConstructSome(t, ExprToString(astmt.Value))}];"); + return; + case NamedTupleAccessExpr { SubExpr: VariableAccessExpr } tax: + var subExpr = ExprToString(tax.SubExpr); + var entry = tax.Entry.Name; + var field = tax.FieldName; + var fields = ((NamedTupleType)((TypeDefType)tax.SubExpr.Type).TypeDefDecl.Type).Fields; + var rhs = ExprToString(astmt.Value); + var build = string.Join(", ", + fields.Select( + f => f.Name == entry ? $"{entry} := {rhs}" : $"{f.Name} := {subExpr}.{f.Name}")); + EmitLine($"{subExpr} = const_record({build});"); + return; + } + + throw new NotSupportedException( + $"Not supported assignment expression: {astmt.Location} = {astmt.Value} ({GetLocation(astmt)})"); + case IfStmt ifstmt: + var cond = (ifstmt.Condition) switch + { + NondetExpr => "*", + _ => ExprToString(ifstmt.Condition), + }; + EmitLine($"if ({cond}) {{"); + GenerateStmt(ifstmt.ThenBranch, specMachine, goals, generateSanityChecks); + EmitLine("} else {"); + GenerateStmt(ifstmt.ElseBranch, specMachine, goals, generateSanityChecks); + EmitLine("}"); + return; + case AssertStmt astmt: + EmitLine($"// {((StringExpr)astmt.Message).BaseString}"); + EmitLine( + $"assert({ExprToString(astmt.Assertion)}); // Failed to verify assertion at {GetLocation(astmt)}"); + return; + case AssumeStmt astmt: + EmitLine($"// {((StringExpr)astmt.Message).BaseString}"); + EmitLine($"assume({ExprToString(astmt.Assumption)});"); + return; + case PrintStmt { Message: StringExpr } pstmt: + EmitLine($"// {((StringExpr)pstmt.Message).BaseString}"); + return; + case FunCallStmt fapp: + EmitLine( + $"call {fapp.Function.Name}({string.Join(", ", fapp.ArgsList.Select(ExprToString).Prepend("this"))});"); + return; + case AddStmt { Variable: VariableAccessExpr } astmt: + // v += (y) + var aset = ExprToString(astmt.Variable); + var akey = ExprToString(astmt.Value); + EmitLine($"{aset} = {aset}[{akey} -> true];"); + return; + case AddStmt { Variable: MapAccessExpr } astmt + when ((MapAccessExpr)astmt.Variable).MapExpr is VariableAccessExpr: + // m[x] += (y) + // m = m[x -> some((m[x].some)[y -> true])] + var mapInP = ((MapAccessExpr)astmt.Variable); + var mapTypeInP = (MapType)mapInP.MapExpr.Type; + var mapM = ExprToString(mapInP.MapExpr); + var locX = ExprToString(mapInP.IndexExpr); + var valY = ExprToString(astmt.Value); + var someMx = OptionSelectValue(mapTypeInP.ValueType, $"{mapM}[{locX}]"); + var someUpdated = OptionConstructSome(mapTypeInP.ValueType, $"{someMx}[{valY} -> true]"); + EmitLine($"{mapM} = {mapM}[{locX} -> {someUpdated}];"); + return; + case RemoveStmt { Variable: VariableAccessExpr } rstmt: + var rset = ExprToString(rstmt.Variable); + var rkey = ExprToString(rstmt.Value); + + switch (rstmt.Variable.Type) + { + case MapType mapType: + EmitLine($"{rset} = {rset}[{rkey} -> {OptionConstructNone(mapType.ValueType)}];"); + return; + case SetType _: + EmitLine($"{rset} = {rset}[{rkey} -> false];"); + return; + default: + throw new NotSupportedException( + $"Only support remove statements for sets and maps, got {rstmt.Variable.Type}"); + } + case InsertStmt { Variable: VariableAccessExpr } istmt: + var imap = ExprToString(istmt.Variable); + var idx = ExprToString(istmt.Index); + var value = OptionConstructSome(istmt.Value.Type, ExprToString(istmt.Value)); + EmitLine($"{imap} = {imap}[{idx} -> {value}];"); + return; + case ForeachStmt fstmt: + + switch (fstmt.IterCollection) + { + case KeysExpr keysExpr: + fstmt = new ForeachStmt(fstmt.SourceLocation, fstmt.Item, keysExpr.Expr, fstmt.Body, + fstmt.Invariants); + break; + } + + var item = GetLocalName(fstmt.Item); + var checker = GetCheckerName(fstmt.IterCollection.Type); + var collection = ExprToString(fstmt.IterCollection); + + switch (fstmt.IterCollection.Type) + { + case SetType setType: + // set the checker to default + EmitLine($"{checker} = {DefaultValue(setType)};"); + // remember to declare it later + _setCheckersToDeclare.Add(setType); + // havoc the item + EmitLine($"havoc {item};"); + EmitLine($"while ({checker} != {collection})"); + foreach (var inv in goals) + { + if (!inv.IsDefault) { + EmitLine($"\tinvariant {InvariantPrefix}{inv.Name}(); // Failed to verify invariant {inv.Name.Replace("_PGROUP_", ": ")} at {GetLocation(fstmt)}"); + } + + } + + if (generateSanityChecks) + { + EmitLine($"\tinvariant {InvariantPrefix}Unique_Actions(); // Failed to ensure unique action IDs at {GetLocation(fstmt)}"); + EmitLine($"\tinvariant {InvariantPrefix}Increasing_Action_Count(); // Failed to ensure increasing action IDs at {GetLocation(fstmt)}"); + EmitLine($"\tinvariant {InvariantPrefix}Received_Subset_Sent(); // Failed to ensure that received is a subset of sent at {GetLocation(fstmt)}"); + } + + // ensure uniqueness for the new ones too + EmitLine( + $"\tinvariant forall (a1: {LabelAdt}, a2: {LabelAdt}) :: (a1 != a2 && {LocalPrefix}sent[a1] && {LocalPrefix}sent[a2]) ==> {LabelAdtSelectActionCount("a1")} != {LabelAdtSelectActionCount("a2")};"); + EmitLine( + $"\tinvariant forall (a: {LabelAdt}) :: {LocalPrefix}sent[a] ==> {LabelAdtSelectActionCount("a")} < {BuiltinPrefix}ActionCount;"); + // ensure we only ever add sends + EmitLine( + $"\tinvariant forall (e: {LabelAdt}) :: {StateAdtSelectSent(StateVar)}[e] ==> {LocalPrefix}sent[e];"); + + // user given invariants + foreach (var inv in fstmt.Invariants) + { + EmitLine( + $"\tinvariant {ExprToString(inv)}; // Failed to verify loop invariant at {GetLocation(inv)}"); + } + + EmitLine("{"); + // assume that the item is in the set but hasn't been visited + EmitLine($"if ({collection}[{item}] && !{checker}[{item}]) {{"); + // the body of the loop + GenerateStmt(fstmt.Body, specMachine, goals, generateSanityChecks); + // update the checker + EmitLine($"{checker} = {checker}[{item} -> true];"); + EmitLine("}"); + // havoc the item + EmitLine($"havoc {item};"); + EmitLine("}"); + return; + case MapType mapType: + // set the checker to default + EmitLine($"{checker} = {DefaultValue(mapType)};"); + // remember to declare it later + _setCheckersToDeclare.Add(mapType); + // havoc the item, in this case it is a key + EmitLine($"havoc {item};"); + EmitLine($"while ({checker} != {collection})"); + foreach (var inv in goals) + { + if (!inv.IsDefault) { + EmitLine($"\tinvariant {InvariantPrefix}{inv.Name}(); // Failed to verify invariant {inv.Name.Replace("_PGROUP_", ": ")} at {GetLocation(fstmt)}"); + } + } + + if (generateSanityChecks) { + EmitLine($"\tinvariant {InvariantPrefix}Unique_Actions(); // Failed to ensure unique action IDs at {GetLocation(fstmt)}"); + EmitLine($"\tinvariant{InvariantPrefix}Increasing_Action_Count(); // Failed to ensure increasing action IDs at {GetLocation(fstmt)}"); + EmitLine($"\tinvariant {InvariantPrefix}Received_Subset_Sent(); // Failed to ensure that received is a subset of sent at {GetLocation(fstmt)}"); + } + // ensure uniqueness for the new ones too + EmitLine( + $"\tinvariant forall (a1: {LabelAdt}, a2: {LabelAdt}) :: (a1 != a2 && {LocalPrefix}sent[a1] && {LocalPrefix}sent[a2]) ==> {LabelAdtSelectActionCount("a1")} != {LabelAdtSelectActionCount("a2")};"); + EmitLine( + $"\tinvariant forall (a: {LabelAdt}) :: {LocalPrefix}sent[a] ==> {LabelAdtSelectActionCount("a")} < {BuiltinPrefix}ActionCount;"); + + // ensure we only ever add sends + EmitLine( + $"\tinvariant forall (e: {LabelAdt}) :: {StateAdtSelectSent(StateVar)}[e] ==> {LocalPrefix}sent[e];"); + + // user given invariants + foreach (var inv in fstmt.Invariants) + { + EmitLine( + $"\tinvariant {ExprToString(inv)}; // Failed to verify loop invariant at {GetLocation(inv)}"); + } + + EmitLine("{"); + // assume that the item is in the set but hasn't been visited + EmitLine( + $"if ({OptionIsSome(mapType.ValueType, $"{collection}[{item}]")} && {OptionIsNone(mapType.ValueType, $"{checker}[{item}]")}) {{"); + // the body of the loop + GenerateStmt(fstmt.Body, specMachine, goals, generateSanityChecks); + // update the checker + EmitLine($"{checker} = {checker}[{item} -> {collection}[{item}]];"); + EmitLine("}"); + // havoc the item + EmitLine($"havoc {item};"); + EmitLine("}"); + return; + default: + throw new NotSupportedException( + $"Foreach over non-sets is not supported yet: {fstmt} ({GetLocation(fstmt)}"); + } + case GotoStmt gstmt when specMachine is null: + var gaction = EventOrGotoAdtConstructGoto(gstmt.State, gstmt.Payload); + var glabel = LabelAdtConstruct("this", gaction); + var glabels = StateAdtSelectSent(StateVar); + var newState = $"{MachinePrefix}{gstmt.State.OwningMachine.Name}_{gstmt.State.Name}"; + EmitLine($"{glabels} = {glabels}[{glabel} -> true];"); + IncrementActionCount(); + EmitLine($"{LocalPrefix}state = {newState};"); + EmitLine($"{LocalPrefix}stage = true;"); + return; + case GotoStmt gstmts: // when specMachine is not null + EmitLine($"{LocalPrefix}state = {SpecPrefix}{specMachine.Name}_{gstmts.State.Name};"); + EmitLine($"{LocalPrefix}stage = true;"); + return; + case SendStmt sstmt when specMachine is null: + if (sstmt.Arguments.Count > 1) + { + throw new NotSupportedException("We only support at most one argument to a send"); + } + + var ev = ((EventRefExpr)sstmt.Evt).Value; + var saction = EventOrGotoAdtConstructEvent(ev, sstmt.Arguments.Any() ? sstmt.Arguments.First() : null); + var slabel = LabelAdtConstruct(ExprToString(sstmt.MachineExpr), saction); + var slabels = $"{LocalPrefix}sent"; + EmitLine($"{slabels} = {slabels}[{slabel} -> true];"); + IncrementActionCount(); + foreach (var procedureName in _specListenMap.GetValueOrDefault(ev, [])) + { + var argument = sstmt.Arguments.Count > 0 ? $"{ExprToString(sstmt.Arguments[0])}" : PNull; + EmitLine($"call {procedureName}({argument});"); + } + + return; + case ReturnStmt rstmt: + EmitLine($"{BuiltinPrefix}Return = {ExprToString(rstmt.ReturnValue)};"); + return; + case null: + return; + } + + throw new NotSupportedException($"Not supported statement: {stmt} ({GetLocation(stmt)})"); + } + + private string ExprToString(IPExpr expr) + { + return expr switch + { + NamedTupleAccessExpr ntax => $"{ExprToString(ntax.SubExpr)}.{ntax.FieldName}", + VariableAccessExpr vax => GetLocalName(vax.Variable), + IntLiteralExpr i => i.Value.ToString(), + BoolLiteralExpr b => b.Value.ToString().ToLower(), + BinOpExpr bexpr => + $"({ExprToString(bexpr.Lhs)} {BinOpToString(bexpr.Operation)} {ExprToString(bexpr.Rhs)})", + UnaryOpExpr uexpr => $"({UnaryOpToString(uexpr.Operation)} {ExprToString(uexpr.SubExpr)})", + ThisRefExpr => "this", + EnumElemRefExpr e => $"{UserPrefix}{e.Value.Name}", + NamedTupleExpr t => NamedTupleExprHelper(t), + StringExpr s => + $"\"{s.BaseString}\" {(s.Args.Count != 0 ? "%" : "")} {string.Join(", ", s.Args.Select(ExprToString))}", + MapAccessExpr maex => + OptionSelectValue(((MapType)maex.MapExpr.Type).ValueType, + $"{ExprToString(maex.MapExpr)}[{ExprToString(maex.IndexExpr)}]"), + ContainsExpr cexp when cexp.Collection.Type.Canonicalize() is MapType => OptionIsSome( + ((MapType)cexp.Collection.Type).ValueType, + $"{ExprToString(cexp.Collection)}[{ExprToString(cexp.Item)}]"), + ContainsExpr cexp when cexp.Collection.Type.Canonicalize() is SetType => + $"{ExprToString(cexp.Collection)}[{ExprToString(cexp.Item)}]", + DefaultExpr dexp => DefaultValue(dexp.Type), + QuantExpr { Quant: QuantType.Forall } qexpr => + $"(forall ({BoundVars(qexpr.Bound)}) :: {Guard(qexpr.Bound, qexpr.Difference, true)}({ExprToString(qexpr.Body)}))", + QuantExpr { Quant: QuantType.Exists } qexpr => + $"(exists ({BoundVars(qexpr.Bound)}) :: {Guard(qexpr.Bound, qexpr.Difference, false)}({ExprToString(qexpr.Body)}))", + MachineAccessExpr max => MachineStateAdtSelectField(Deref(ExprToString(max.SubExpr)), max.Machine, + max.Entry), + SpecAccessExpr sax => $"{SpecPrefix}{sax.Spec.Name}_{sax.FieldName}", + EventAccessExpr eax => LabelAdtSelectPayloadField(ExprToString(eax.SubExpr), eax.PEvent, eax.Entry), + TestExpr { Kind: Machine m } texpr => MachineStateAdtIsM(Deref(ExprToString(texpr.Instance)), + m), // must deref because or else we don't have an ADT! + TestExpr { Kind: Event e } texpr => LabelAdtIsE(ExprToString(texpr.Instance), e), + TestExpr { Kind: State s } texpr => MachineStateAdtInS(Deref(ExprToString(texpr.Instance)), s.OwningMachine, + s), // must deref for ADT! + PureCallExpr pexpr => $"{pexpr.Pure.Name}({string.Join(", ", pexpr.Arguments.Select(ExprToString))})", + FlyingExpr fexpr => $"{InFlight(StateVar, ExprToString(fexpr.Instance))}", + SentExpr sexpr => useLocalPrefix ? $"{LocalPrefix}sent[{ExprToString(sexpr.Instance)}]" : $"{StateAdtSelectSent(StateVar)}[{ExprToString(sexpr.Instance)}]", + TargetsExpr texpr => + $"({LabelAdtSelectTarget(ExprToString(texpr.Instance))} == {ExprToString(texpr.Target)})", + _ => throw new NotSupportedException($"Not supported expr ({expr}) at {GetLocation(expr)}") + // _ => $"NotHandledExpr({expr})" + }; + + string BoundVars(List bound) + { + return $"{string.Join(", ", bound.Select(b => $"{LocalPrefix}{b.Name}: {TypeToString(b.Type)}"))}"; + } + + string Guard(List bound, bool difference, bool universal) + { + var impliesOrAnd = universal ? "==>" : "&&"; + + List bounds = []; + foreach (var b in bound) + { + switch (b.Type) + { + case PermissionType { Origin: Machine } pt: + bounds.Add(ExprToString(new TestExpr(b.SourceLocation, + new VariableAccessExpr(b.SourceLocation, b), pt.Origin))); + break; + case PermissionType { Origin: Interface } pt: + var name = pt.OriginalRepresentation; + + if (_globalScope.Lookup(name, out Machine m)) + { + bounds.Add(ExprToString(new TestExpr(b.SourceLocation, + new VariableAccessExpr(b.SourceLocation, b), m))); + } + + break; + case PermissionType { Origin: NamedEventSet } pt: + var e = ((NamedEventSet)pt.Origin).Events.First(); + bounds.Add(ExprToString(new TestExpr(b.SourceLocation, + new VariableAccessExpr(b.SourceLocation, b), e))); + if (difference) + { + bounds.Add($"{LocalPrefix}sent[{LocalPrefix}{b.Name}]"); + bounds.Add($"!{StateAdtSelectSent(StateVar)}[{LocalPrefix}{b.Name}]"); + } + + break; + case PrimitiveType pt when pt.IsSameTypeAs(PrimitiveType.Event): + if (difference) + { + bounds.Add($"{LocalPrefix}sent[{LocalPrefix}{b.Name}]"); + bounds.Add($"!{StateAdtSelectSent(StateVar)}[{LocalPrefix}{b.Name}]"); + } + + break; + } + } + + if (bounds.Count != 0) + { + return "(" + string.Join(" && ", bounds) + $") {impliesOrAnd} "; + } + + return ""; + } + } + + private string NamedTupleExprHelper(NamedTupleExpr t) + { + var ty = (NamedTupleType)t.Type; + var names = ty.Fields.Select(f => f.Name); + var values = t.TupleFields.Select(ExprToString); + var args = string.Join(", ", names.Zip(values).Select(p => $"{p.First} := {p.Second}")); + return $"const_record({args})"; + } + + private static string BinOpToString(BinOpType op) + { + return op switch + { + BinOpType.Add => "+", + BinOpType.Sub => "-", + BinOpType.Mul => "*", + BinOpType.Div => "/", + BinOpType.Mod => "%", + BinOpType.Lt => "<", + BinOpType.Le => "<=", + BinOpType.Gt => ">", + BinOpType.Ge => ">=", + BinOpType.And => "&&", + BinOpType.Or => "||", + BinOpType.Eq => "==", + BinOpType.Neq => "!=", + BinOpType.Then => "==>", + BinOpType.Iff => "==", + _ => throw new NotImplementedException($"{op} is not implemented yet!") + }; + } + + private static string UnaryOpToString(UnaryOpType op) + { + return op switch + { + UnaryOpType.Negate => "-", + UnaryOpType.Not => "!", + _ => throw new NotImplementedException($"{op} is not implemented yet!") + }; + } + + private string TypeToString(PLanguageType t) + { + switch (t.Canonicalize()) + { + case NamedTupleType ntt: + var fields = string.Join(", ", + ntt.Fields.Select(nte => $"{nte.Name}: {TypeToString(nte.Type)}")); + return $"record {{{fields}}}"; + case PrimitiveType pt when pt.Equals(PrimitiveType.Bool): + return "boolean"; + case PrimitiveType pt when pt.Equals(PrimitiveType.Int): + return "integer"; + case PrimitiveType pt when pt.Equals(PrimitiveType.String): + return StringT; + case PrimitiveType pt when pt.Equals(PrimitiveType.Null): + return PNull; + case PrimitiveType pt when pt.Equals(PrimitiveType.Machine): + return MachineRefT; + case PrimitiveType pt when pt.Equals(PrimitiveType.Event): + return LabelAdt; + case TypeDefType tdt: + return $"{UserPrefix}{tdt.TypeDefDecl.Name}"; + case PermissionType { Origin: Machine } _: + return MachineRefT; + case PermissionType { Origin: Interface } _: + return MachineRefT; + case PermissionType { Origin: NamedEventSet } _: + return LabelAdt; + case EnumType et: + return $"{UserPrefix}{et.EnumDecl.Name}"; + case SetType st: + return $"[{TypeToString(st.ElementType)}]boolean"; + case MapType mt: + _optionsToDeclare.Add(mt.ValueType.Canonicalize()); + return $"[{TypeToString(mt.KeyType)}]{GetOptionName(mt.ValueType)}"; + } + + throw new NotSupportedException($"Not supported type expression {t} ({t.OriginalRepresentation})"); + } + + private string GetLocalName(Variable v) + { + return $"{LocalPrefix}{v.Name}"; + } + + + private string GetCheckerName(PLanguageType t) + { + var output = $"{CheckerPrefix}{TypeToString(t)}"; + return Regex.Replace(output, "[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled); + } + + private string GetOptionName(PLanguageType t) + { + var output = $"{OptionPrefix}{TypeToString(t)}"; + return Regex.Replace(output, "[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled); + } + + + private string GetChooseName(PLanguageType t) + { + var output = $"{ChoosePrefix}{TypeToString(t)}"; + return Regex.Replace(output, "[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled); + } + + private string GetLocation(IPAST node) + { + return _ctx.LocationResolver.GetLocation(node.SourceLocation).ToString(); + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/Backend/TargetLanguage.cs b/Src/PCompiler/CompilerCore/Backend/TargetLanguage.cs index 7d6f938eea..3b55c64075 100644 --- a/Src/PCompiler/CompilerCore/Backend/TargetLanguage.cs +++ b/Src/PCompiler/CompilerCore/Backend/TargetLanguage.cs @@ -3,6 +3,7 @@ using Plang.Compiler.Backend.Java; using Plang.Compiler.Backend.PEx; using Plang.Compiler.Backend.Stately; +using Plang.Compiler.Backend.PVerifier; namespace Plang.Compiler.Backend { @@ -16,6 +17,7 @@ static TargetLanguage() RegisterCodeGenerator(CompilerOutput.PChecker, new PCheckerCodeGenerator()); RegisterCodeGenerator(CompilerOutput.PObserve, new PObserveCodeGenerator()); RegisterCodeGenerator(CompilerOutput.Stately, new StatelyCodeGenerator()); + RegisterCodeGenerator(CompilerOutput.PVerifier, new PVerifierCodeGenerator()); RegisterCodeGenerator(CompilerOutput.PEx, new PExCodeGenerator()); } diff --git a/Src/PCompiler/CompilerCore/Compiler.cs b/Src/PCompiler/CompilerCore/Compiler.cs index 9fc7990a01..a71b5fe1fd 100644 --- a/Src/PCompiler/CompilerCore/Compiler.cs +++ b/Src/PCompiler/CompilerCore/Compiler.cs @@ -49,12 +49,23 @@ public int Compile(ICompilerConfiguration job) return Environment.ExitCode; } - // Convert functions to lowered SSA form with explicit cloning - foreach (var fun in scope.GetAllMethods()) + if (job.OutputLanguages.Contains(CompilerOutput.PVerifier)) { - IRTransformer.SimplifyMethod(fun); + // If using the PVerifier, don't output anything else + if (job.OutputLanguages.Count > 1) + { + throw new NotSupportedException("PVerifier backend must be used on its own!"); + } } - + else + { + // Convert functions to lowered SSA form with explicit cloning + foreach (var fun in scope.GetAllMethods()) + { + IRTransformer.SimplifyMethod(fun); + } + } + DirectoryInfo parentDirectory = job.OutputDirectory; foreach (var entry in job.OutputLanguages.Distinct()) { @@ -80,7 +91,10 @@ public int Compile(ICompilerConfiguration job) foreach (var file in compiledFiles) { - job.Output.WriteInfo($"Generated {file.FileName}."); + if (entry != CompilerOutput.PVerifier) + { + job.Output.WriteInfo($"Generated {file.FileName}."); + } job.Output.WriteFile(file); } @@ -121,6 +135,41 @@ private static PParser.ProgramContext Parse(ICompilerConfiguration job, FileInfo var lexer = new PLexer(fileStream); lexer.AddErrorListener(new PLexerErrorListener(inputFile, job.Handler)); var tokens = new CommonTokenStream(lexer); + + if (!job.OutputLanguages.Contains(CompilerOutput.PVerifier)) + { + // disallow any pverifier tokens + tokens.Fill(); + foreach (var token in tokens.GetTokens()) + { + switch (token.Type) + { + case PParser.INVARIANT: + case PParser.AXIOM: + case PParser.IS: + case PParser.FLYING: + case PParser.TARGETS: + case PParser.SENT: + case PParser.PROOF: + case PParser.PROVE: + case PParser.USING: + case PParser.LEMMA: + case PParser.THEOREM: + case PParser.EXCEPT: + case PParser.REQUIRES: + case PParser.ENSURES: + case PParser.FORALL: + case PParser.EXISTS: + case PParser.INIT: + case PParser.PURE: + throw new NotSupportedException( + $"line {token.Line}:{token.Column} \"{token.Text}\" only supported by PVerifier backend."); + } + } + + tokens.Reset(); + } + var parser = new PParser(tokens); parser.RemoveErrorListeners(); @@ -192,6 +241,43 @@ public void SyntaxError(IRecognizer recognizer, IToken offendingSymbol, int line } } + public static Process NonBlockingRun(string activeDirectory, string exeName, params string[] argumentList) + { + var psi = new ProcessStartInfo(exeName) + { + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardInput = true, + RedirectStandardOutput = true, + WorkingDirectory = activeDirectory, + Arguments = string.Join(" ", argumentList) + }; + + var proc = new Process { StartInfo = psi }; + proc.Start(); + return proc; + } + + public static int WaitForResult(Process proc, out string stdout, out string stderr) + { + string mStderr = "", mStdout = ""; + proc.OutputDataReceived += (s, e) => { mStdout += $"{e.Data}\n"; }; + proc.ErrorDataReceived += (s, e) => + { + if (!string.IsNullOrWhiteSpace(e.Data)) + { + mStderr += $"{e.Data}\n"; + } + }; + proc.BeginErrorReadLine(); + proc.BeginOutputReadLine(); + proc.WaitForExit(); + stdout = mStdout; + stderr = mStderr; + return proc.ExitCode; + } + public static int RunWithOutput(string activeDirectory, out string stdout, out string stderr, string exeName, diff --git a/Src/PCompiler/CompilerCore/CompilerConfiguration.cs b/Src/PCompiler/CompilerCore/CompilerConfiguration.cs index 3a4dc3e3fe..a735ff46b0 100644 --- a/Src/PCompiler/CompilerCore/CompilerConfiguration.cs +++ b/Src/PCompiler/CompilerCore/CompilerConfiguration.cs @@ -42,6 +42,10 @@ public CompilerConfiguration() Backend = null; ProjectDependencies = new List(); Debug = false; + Timeout = 600; + CheckOnly = null; + TargetProofBlocks = new List(); + Parallelism = Math.Max(Environment.ProcessorCount / 2, 1); } ///

/// Initializes a new instance of the class with specific settings. @@ -154,6 +158,10 @@ public CompilerConfiguration(ICompilerOutput output, DirectoryInfo outputDir, IL /// Gets or sets a value indicating whether debug information should be included in output. /// public bool Debug { get; set; } + public int Timeout { get; set; } + public string CheckOnly { get; set; } + public IList TargetProofBlocks { get; set; } + public int Parallelism { get; set; } /// /// Copies all properties from another CompilerConfiguration instance to this instance. diff --git a/Src/PCompiler/CompilerCore/CompilerCore.csproj b/Src/PCompiler/CompilerCore/CompilerCore.csproj index c3cf7ad18e..a226971419 100644 --- a/Src/PCompiler/CompilerCore/CompilerCore.csproj +++ b/Src/PCompiler/CompilerCore/CompilerCore.csproj @@ -9,6 +9,8 @@ + + diff --git a/Src/PCompiler/CompilerCore/CompilerOutput.cs b/Src/PCompiler/CompilerCore/CompilerOutput.cs index 90fe403c1a..d0be824fee 100644 --- a/Src/PCompiler/CompilerCore/CompilerOutput.cs +++ b/Src/PCompiler/CompilerCore/CompilerOutput.cs @@ -5,6 +5,7 @@ public enum CompilerOutput PChecker, PObserve, Stately, - PEx + PEx, + PVerifier } } \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/DefaultTranslationErrorHandler.cs b/Src/PCompiler/CompilerCore/DefaultTranslationErrorHandler.cs index 9b1bc78458..29f8b9bea2 100644 --- a/Src/PCompiler/CompilerCore/DefaultTranslationErrorHandler.cs +++ b/Src/PCompiler/CompilerCore/DefaultTranslationErrorHandler.cs @@ -78,6 +78,11 @@ public Exception InvalidTwise(ParserRuleContext location, IPDecl testDecl, strin $"invalid twise number at {locationResolver.GetLocation(testDecl.SourceLocation)}: {errMsg}"); } + public Exception CyclicProof(ParserRuleContext location, ProofCommand cmd) + { + return IssueError(location, + $"Proof commands form a cycle at {locationResolver.GetLocation(cmd.SourceLocation)}"); + } public Exception IncorrectArgumentCount(ParserRuleContext location, int actualCount, int expectedCount) { return IssueError(location, @@ -142,6 +147,17 @@ public Exception MissingNamedTupleEntry(PParser.IdenContext location, $"named tuple type {namedTuple.OriginalRepresentation} has no '{location.GetText()}' field"); } + public Exception MissingMachineField(PParser.IdenContext location, Machine machine) + { + return IssueError(location, + $"machine {machine.Name} has no '{location.GetText()}' field"); + } + public Exception MissingEventField(PParser.IdenContext location, Event pevent) + { + return IssueError(location, + $"machine {pevent.Name} payload has no '{location.GetText()}' field"); + } + public Exception OutOfBoundsTupleAccess(PParser.IntContext location, TupleType tuple) { return IssueError( diff --git a/Src/PCompiler/CompilerCore/ICompilerConfiguration.cs b/Src/PCompiler/CompilerCore/ICompilerConfiguration.cs index 1aa20f9dc8..17d9679356 100644 --- a/Src/PCompiler/CompilerCore/ICompilerConfiguration.cs +++ b/Src/PCompiler/CompilerCore/ICompilerConfiguration.cs @@ -20,5 +20,9 @@ public interface ICompilerConfiguration ILocationResolver LocationResolver { get; } ITranslationErrorHandler Handler { get; } bool Debug { get; } + int Timeout { get; } + string CheckOnly { get; } + IList TargetProofBlocks { get; } + int Parallelism { get; } } } \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/ITranslationErrorHandler.cs b/Src/PCompiler/CompilerCore/ITranslationErrorHandler.cs index 98b362e1c1..e758c02954 100644 --- a/Src/PCompiler/CompilerCore/ITranslationErrorHandler.cs +++ b/Src/PCompiler/CompilerCore/ITranslationErrorHandler.cs @@ -24,6 +24,7 @@ Exception DuplicateStartState( Exception DuplicateStateEntry(ParserRuleContext location, Function existingHandler, State state); Exception DuplicateDeclaration(ParserRuleContext location, IPDecl duplicate, IPDecl existing); + Exception CyclicProof(ParserRuleContext location, ProofCommand cmd); Exception RedeclareGlobalParam(ParserRuleContext location, IPDecl duplicate, IPDecl existing); @@ -46,6 +47,8 @@ Exception DuplicateStartState( Exception TypeMismatch(IPExpr expr, params TypeKind[] expected); Exception MissingNamedTupleEntry(PParser.IdenContext location, NamedTupleType namedTuple); + Exception MissingMachineField(PParser.IdenContext location, Machine machine); + Exception MissingEventField(PParser.IdenContext location, Event pevent); Exception OutOfBoundsTupleAccess(PParser.IntContext location, TupleType tuple); diff --git a/Src/PCompiler/CompilerCore/Parser/PLexer.g4 b/Src/PCompiler/CompilerCore/Parser/PLexer.g4 index 807b62ac17..828cbf5fe4 100644 --- a/Src/PCompiler/CompilerCore/Parser/PLexer.g4 +++ b/Src/PCompiler/CompilerCore/Parser/PLexer.g4 @@ -22,7 +22,6 @@ DATA : 'data' ; ANNOUNCE : 'announce' ; AS : 'as' ; ASSERT : 'assert' ; -ASSUME : 'assume' ; BREAK : 'break' ; CASE : 'case' ; COLD : 'cold' ; @@ -66,6 +65,27 @@ WHILE : 'while' ; WITH : 'with' ; CHOOSE : 'choose' ; +// PVerifier keywords +INVARIANT : 'invariant'; +AXIOM : 'axiom'; +IS : 'is'; +FLYING : 'inflight'; +TARGETS : 'targets'; +SENT : 'sent'; +PROOF : 'Proof'; +PROVE : 'prove'; +USING : 'using'; +LEMMA : 'Lemma'; +THEOREM : 'Theorem'; +EXCEPT : 'except'; +REQUIRES : 'requires'; +ENSURES : 'ensures'; +FORALL : 'forall' ; +EXISTS : 'exists' ; +INIT : 'init-condition'; +PURE : 'pure' ; +ASSUME : 'assume' ; + // module-system-specific keywords // module-test-implementation declarations @@ -113,6 +133,8 @@ NONDET : '$' ; LNOT : '!' ; LAND : '&&' ; LOR : '||' ; +LTHEN : '==>'; +LIFF : '<==>'; EQ : '==' ; NE : '!=' ; diff --git a/Src/PCompiler/CompilerCore/Parser/PParser.g4 b/Src/PCompiler/CompilerCore/Parser/PParser.g4 index 4f65089fcf..5d5ee15eae 100644 --- a/Src/PCompiler/CompilerCore/Parser/PParser.g4 +++ b/Src/PCompiler/CompilerCore/Parser/PParser.g4 @@ -57,12 +57,25 @@ topDecl : typeDefDecl | implMachineDecl | specMachineDecl | funDecl + | pureDecl | namedModuleDecl | testDecl | implementationDecl | globalParamDecl + | invariantDecl + | invariantGroupDecl + | axiomDecl + | assumeOnStartDecl + | proofBlockDecl ; +invariantGroupDecl : LEMMA name=iden LBRACE invariantDecl* RBRACE + | THEOREM name=iden LBRACE invariantDecl* RBRACE + ; + +proofBlockDecl : PROOF (name=iden)? LBRACE proofBody RBRACE # ProofBlock ; +proofBody : proofItem* ; +proofItem : PROVE (targets+=expr (COMMA targets+=expr)* | goalsAll=MUL | goalsDefault=DEFAULT) (USING ((premises+=expr (COMMA premises+=expr)*) | premisesAll=MUL))? (EXCEPT excludes+=expr (COMMA excludes+=expr)*)? SEMI # ProveUsingCmd ; globalParamDecl : PARAM idenList COLON type SEMI ; typeDefDecl : TYPE name=iden SEMI # ForeignTypeDef @@ -77,10 +90,7 @@ enumElem : name=iden ; numberedEnumElemList : numberedEnumElem (COMMA numberedEnumElem)* ; numberedEnumElem : name=iden ASSIGN value=IntLiteral ; -eventDecl : EVENT name=iden cardinality? (COLON type)? SEMI; -cardinality : ASSERT IntLiteral - | ASSUME IntLiteral - ; +eventDecl : EVENT name=iden (COLON type)? SEMI; eventSetDecl : EVENTSET name=iden ASSIGN LBRACE eventSetLiteral RBRACE SEMI ; eventSetLiteral : events+=nonDefaultEvent (COMMA events+=nonDefaultEvent)* ; @@ -88,7 +98,8 @@ eventSetLiteral : events+=nonDefaultEvent (COMMA events+=nonDefaultEvent)* ; interfaceDecl : INTERFACE name=iden LPAREN type? RPAREN (RECEIVES nonDefaultEventList?) SEMI ; // has scope -implMachineDecl : MACHINE name=iden cardinality? receivesSends* machineBody ; +implMachineDecl : MACHINE name=iden receivesSends* machineBody ; + idenList : names+=iden (COMMA names+=iden)* ; receivesSends : RECEIVES eventSetLiteral? SEMI # MachineReceive @@ -107,7 +118,15 @@ varDecl : VAR idenList COLON type SEMI ; funDecl : FUN name=iden LPAREN funParamList? RPAREN (COLON type)? (CREATES interfaces+=iden)? SEMI # ForeignFunDecl | FUN name=iden LPAREN funParamList? RPAREN (COLON type)? functionBody # PFunDecl + | FUN name=iden LPAREN funParamList? RPAREN (RETURN LPAREN funParam RPAREN SEMI)? (REQUIRES requires+=expr SEMI)* (ENSURES ensures+=expr SEMI)* # ForeignFunDecl ; + +pureDecl : PURE name=iden LPAREN funParamList? RPAREN COLON type (ASSIGN body=expr)? SEMI ; + +invariantDecl: INVARIANT name=iden COLON body=expr SEMI ; +axiomDecl: AXIOM body=expr SEMI ; + +assumeOnStartDecl: INIT body=expr SEMI ; stateDecl : START? temperature=(HOT | COLD)? STATE name=iden LBRACE stateBodyItem* RBRACE ; @@ -135,6 +154,7 @@ stateName : state=iden ; functionBody : LBRACE varDecl* statement* RBRACE ; statement : LBRACE statement* RBRACE # CompoundStmt | ASSERT assertion=expr (COMMA message=expr)? SEMI # AssertStmt + | ASSUME assumption=expr (COMMA message=expr)? SEMI # AssumeStmt | PRINT message=expr SEMI # PrintStmt | RETURN expr? SEMI # ReturnStmt | BREAK SEMI # BreakStmt @@ -144,8 +164,9 @@ statement : LBRACE statement* RBRACE # CompoundStmt | lvalue INSERT LPAREN rvalue RPAREN SEMI # AddStmt | lvalue REMOVE expr SEMI # RemoveStmt | WHILE LPAREN expr RPAREN statement # WhileStmt - | FOREACH LPAREN item=iden IN collection=expr - RPAREN statement # ForeachStmt + | FOREACH LPAREN item=iden IN collection=expr RPAREN + (INVARIANT invariants+=expr SEMI)* + statement # ForeachStmt | IF LPAREN expr RPAREN thenBranch=statement (ELSE elseBranch=statement)? # IfStmt | NEW iden LPAREN rvalueList? RPAREN SEMI # CtorStmt @@ -175,6 +196,10 @@ expr : primitive # PrimitiveExpr | LPAREN expr RPAREN # ParenExpr | expr DOT field=iden # NamedTupleAccessExpr | expr DOT field=int # TupleAccessExpr + | instance=expr IS kind=iden # TestExpr + | instance=expr TARGETS target=expr # TargetsExpr + | FLYING instance=expr # FlyingExpr + | SENT instance=expr # SentExpr | seq=expr LBRACK index=expr RBRACK # SeqAccessExpr | fun=KEYS LPAREN expr RPAREN # KeywordExpr | fun=VALUES LPAREN expr RPAREN # KeywordExpr @@ -191,6 +216,12 @@ expr : primitive # PrimitiveExpr | lhs=expr op=(EQ | NE) rhs=expr # BinExpr | lhs=expr op=LAND rhs=expr # BinExpr | lhs=expr op=LOR rhs=expr # BinExpr + | lhs=expr op=LTHEN rhs=expr # BinExpr + | lhs=expr op=LIFF rhs=expr # BinExpr + | quant=(FORALL | EXISTS) + diff=NEW? + LPAREN bound=funParamList RPAREN + COLON COLON body=expr # QuantExpr | CHOOSE LPAREN expr? RPAREN # ChooseExpr | formatedString # StringExpr ; diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/AssumeOnStart.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/AssumeOnStart.cs new file mode 100644 index 0000000000..7d8cc0f53c --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/AssumeOnStart.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Antlr4.Runtime; +using Plang.Compiler.TypeChecker.AST.Expressions; + +namespace Plang.Compiler.TypeChecker.AST.Declarations +{ + public class AssumeOnStart : IPDecl + { + public AssumeOnStart(string name, IPExpr body, ParserRuleContext sourceNode) + { + Debug.Assert(sourceNode is PParser.AssumeOnStartDeclContext); + Body = body; + Name = name; + SourceLocation = sourceNode; + } + + public IPExpr Body { get; set; } + + public string Name { get; set; } + public ParserRuleContext SourceLocation { get; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Axiom.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Axiom.cs new file mode 100644 index 0000000000..a646cc76f3 --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Axiom.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Antlr4.Runtime; +using Plang.Compiler.TypeChecker.AST.Expressions; + +namespace Plang.Compiler.TypeChecker.AST.Declarations +{ + public class Axiom : IPDecl + { + public Axiom(string name, IPExpr body, ParserRuleContext sourceNode) + { + Debug.Assert(sourceNode is PParser.AxiomDeclContext); + Name = name; + Body = body; + SourceLocation = sourceNode; + } + + public IPExpr Body { get; set; } + + public string Name { get; set; } + public ParserRuleContext SourceLocation { get; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Event.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Event.cs index 961b2d0c46..0b3fb07c7e 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Event.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Event.cs @@ -14,12 +14,7 @@ public Event(string name, ParserRuleContext sourceNode) Name = name; SourceLocation = sourceNode; PayloadType = PrimitiveType.Null; - Assert = -1; - Assume = -1; } - - public int Assume { get; set; } - public int Assert { get; set; } public PLanguageType PayloadType { get; set; } public bool IsHaltEvent => string.Equals(Name, "halt"); diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Function.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Function.cs index 10c2599006..d1633e6078 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Function.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Function.cs @@ -23,6 +23,8 @@ public class Function : IPDecl, IHasScope private readonly HashSet callees = new HashSet(); private readonly HashSet callers = new HashSet(); private readonly List localVariables = new List(); + private readonly List requireExprs = new List(); + private readonly List ensureExprs = new List(); private readonly List createsInterfaces = new List(); public Function(string name, ParserRuleContext sourceNode) @@ -57,6 +59,9 @@ public Function(ParserRuleContext sourceNode) : this("", sourceNode) public Function ParentFunction { get; set; } public FunctionSignature Signature { get; } = new FunctionSignature(); public IEnumerable LocalVariables => localVariables; + public Variable ReturnVariable { get; set; } + public IEnumerable Requires => requireExprs; + public IEnumerable Ensures => ensureExprs; public IEnumerable CreatesInterfaces => createsInterfaces; public FunctionRole Role { get; set; } @@ -70,7 +75,15 @@ public void AddLocalVariable(Variable local) { localVariables.Add(local); } - + public void AddRequire(IPExpr expr) + { + requireExprs.Add(expr); + } + public void AddEnsure(IPExpr expr) + { + ensureExprs.Add(expr); + } + public void RemoveLocalVariable(Variable local) { localVariables.Remove(local); diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Invariant.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Invariant.cs new file mode 100644 index 0000000000..864fdf6f5c --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Invariant.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Antlr4.Runtime; +using Plang.Compiler.TypeChecker.AST.Expressions; + +namespace Plang.Compiler.TypeChecker.AST.Declarations +{ + public class Invariant : IPDecl + { + public Invariant(string name, IPExpr body, ParserRuleContext sourceNode) + { + Debug.Assert(sourceNode is PParser.InvariantDeclContext); + IsDefault = false; + Name = name; + Body = body; + SourceLocation = sourceNode; + } + + public Invariant(ParserRuleContext sourceNode) + { + SourceLocation = sourceNode; + IsDefault = true; + Name = "default"; + } + + public IPExpr Body { get; set; } + + public string Name { get; set; } + + public bool IsDefault { get; } + public ParserRuleContext SourceLocation { get; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/InvariantGroup.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/InvariantGroup.cs new file mode 100644 index 0000000000..75689cbbe0 --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/InvariantGroup.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Antlr4.Runtime; + +namespace Plang.Compiler.TypeChecker.AST.Declarations +{ + public class InvariantGroup : IPDecl + { + public InvariantGroup(string name, ParserRuleContext sourceNode) + { + Name = name; + SourceLocation = sourceNode; + } + public List Invariants { get; set; } + public ParserRuleContext SourceLocation { get; } + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Machine.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Machine.cs index 3b360419e8..f7dc50e146 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Machine.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Machine.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -83,5 +84,11 @@ public void AddFields(IEnumerable variables) { fields.AddRange(variables); } + + public bool LookupEntry(string name, out Variable entry) + { + var lookupTable = fields.ToDictionary(f => f.Name, f => f); + return lookupTable.TryGetValue(name, out entry); + } } } \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/ProofBlock.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/ProofBlock.cs new file mode 100644 index 0000000000..155f683528 --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/ProofBlock.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Antlr4.Runtime; + +namespace Plang.Compiler.TypeChecker.AST.Declarations +{ + public class ProofBlock : IPDecl + { + public ProofBlock(string name, ParserRuleContext sourceNode) + { + Debug.Assert(sourceNode is PParser.ProofBlockContext); + SourceLocation = sourceNode; + Name = name; + } + public ParserRuleContext SourceLocation { get; } + public List Commands { get; set; } + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/ProofCommand.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/ProofCommand.cs new file mode 100644 index 0000000000..14995a4c09 --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/ProofCommand.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Antlr4.Runtime; + +namespace Plang.Compiler.TypeChecker.AST.Declarations +{ + public class ProofCommand : IPDecl + { + public ProofCommand(string name, ParserRuleContext sourceNode) + { + Debug.Assert(sourceNode is PParser.ProofItemContext); + SourceLocation = sourceNode; + Name = name; + } + + public List Goals { get; set; } + public List Premises { get; set; } + public List Excepts { get; set; } + public ParserRuleContext SourceLocation { get; } + public string Name { get; set; } + public string ProofBlock { get; set; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Pure.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Pure.cs new file mode 100644 index 0000000000..acb93fdf78 --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Declarations/Pure.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Antlr4.Runtime; +using Plang.Compiler.TypeChecker.AST.Statements; + +namespace Plang.Compiler.TypeChecker.AST.Declarations +{ + public class Pure : IPDecl, IHasScope + { + + public Pure(string name, ParserRuleContext sourceNode) + { + Debug.Assert(sourceNode is PParser.PureDeclContext); + Name = name; + SourceLocation = sourceNode; + } + + public FunctionSignature Signature { get; } = new FunctionSignature(); + + public string Name { get; set; } + public IPExpr Body { get; set; } + public ParserRuleContext SourceLocation { get; } + + public Scope Scope { get; set; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/BinOpType.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/BinOpType.cs index 30b3adc2b7..3172b00422 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/BinOpType.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/BinOpType.cs @@ -16,7 +16,9 @@ public enum BinOpType Gt, Ge, And, - Or + Or, + Then, + Iff } public enum BinOpKind @@ -56,6 +58,8 @@ public static BinOpKind GetKind(this BinOpType op) // Boolean operators: case BinOpType.And: case BinOpType.Or: + case BinOpType.Then: + case BinOpType.Iff: return BinOpKind.Boolean; // This should be dead code. diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/EventAccessExpr.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/EventAccessExpr.cs new file mode 100644 index 0000000000..7af5c1ced0 --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/EventAccessExpr.cs @@ -0,0 +1,25 @@ +using Antlr4.Runtime; +using Plang.Compiler.TypeChecker.AST.Declarations; +using Plang.Compiler.TypeChecker.Types; + +namespace Plang.Compiler.TypeChecker.AST.Expressions +{ + public class EventAccessExpr : IPExpr + { + public EventAccessExpr(ParserRuleContext sourceLocation, Event pevent, IPExpr subExpr, NamedTupleEntry entry) + { + SourceLocation = sourceLocation; + PEvent = pevent; + SubExpr = subExpr; + Entry = entry; + } + + public Event PEvent { get; } + public IPExpr SubExpr { get; } + public NamedTupleEntry Entry { get; } + public string FieldName => Entry.Name; + + public ParserRuleContext SourceLocation { get; } + public PLanguageType Type => Entry.Type; + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/FlyingExpr.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/FlyingExpr.cs new file mode 100644 index 0000000000..2f24f3bb8e --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/FlyingExpr.cs @@ -0,0 +1,23 @@ +using System.Diagnostics; +using Antlr4.Runtime; +using Plang.Compiler.TypeChecker.AST.Declarations; +using Plang.Compiler.TypeChecker.Types; + +namespace Plang.Compiler.TypeChecker.AST.Expressions +{ + public class FlyingExpr : IPExpr + { + public FlyingExpr(ParserRuleContext sourceLocation, IPExpr instance) + { + SourceLocation = sourceLocation; + Instance = instance; + Type = PrimitiveType.Bool; + } + + public IPExpr Instance { get; } + + public ParserRuleContext SourceLocation { get; } + + public PLanguageType Type { get; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/InvariantRefExpr.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/InvariantRefExpr.cs new file mode 100644 index 0000000000..5f3a4361ed --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/InvariantRefExpr.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using Antlr4.Runtime; +using Plang.Compiler.TypeChecker.AST.Declarations; +using Plang.Compiler.TypeChecker.Types; + +namespace Plang.Compiler.TypeChecker.AST.Expressions +{ + public class InvariantRefExpr : IPExpr + { + public InvariantRefExpr(Invariant inv, ParserRuleContext sourceLocation) + { + Invariant = inv; + SourceLocation = sourceLocation; + } + public Invariant Invariant { get; set; } + + public PLanguageType Type => PrimitiveType.Bool; + + public ParserRuleContext SourceLocation { get; set; } + } + + public class InvariantGroupRefExpr : IPExpr { + public InvariantGroupRefExpr(InvariantGroup invGroup, ParserRuleContext sourceLocation) + { + InvariantGroup = invGroup; + SourceLocation = sourceLocation; + } + public InvariantGroup InvariantGroup { get; set; } + public List Invariants => InvariantGroup.Invariants; + + public PLanguageType Type => PrimitiveType.Bool; + + public ParserRuleContext SourceLocation { get; set; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/MachineAccessExpr.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/MachineAccessExpr.cs new file mode 100644 index 0000000000..e97d1c8d77 --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/MachineAccessExpr.cs @@ -0,0 +1,25 @@ +using Antlr4.Runtime; +using Plang.Compiler.TypeChecker.AST.Declarations; +using Plang.Compiler.TypeChecker.Types; + +namespace Plang.Compiler.TypeChecker.AST.Expressions +{ + public class MachineAccessExpr : IPExpr + { + public MachineAccessExpr(ParserRuleContext sourceLocation, Machine machine, IPExpr subExpr, Variable entry) + { + SourceLocation = sourceLocation; + Machine = machine; + SubExpr = subExpr; + Entry = entry; + } + + public Machine Machine { get; } + public IPExpr SubExpr { get; } + public Variable Entry { get; } + public string FieldName => Entry.Name; + + public ParserRuleContext SourceLocation { get; } + public PLanguageType Type => Entry.Type; + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/PureCallExpr.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/PureCallExpr.cs new file mode 100644 index 0000000000..9470331dc8 --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/PureCallExpr.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Antlr4.Runtime; +using Plang.Compiler.TypeChecker.AST.Declarations; +using Plang.Compiler.TypeChecker.Types; + +namespace Plang.Compiler.TypeChecker.AST.Expressions +{ + public class PureCallExpr : IPExpr + { + public PureCallExpr(ParserRuleContext sourceLocation, Pure function, IReadOnlyList arguments) + { + SourceLocation = sourceLocation; + Pure = function; + Arguments = arguments; + Type = function.Signature.ReturnType; + } + + public Pure Pure { get; } + public IReadOnlyList Arguments { get; } + + public ParserRuleContext SourceLocation { get; } + + public PLanguageType Type { get; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/QuantExpr.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/QuantExpr.cs new file mode 100644 index 0000000000..160e0adcca --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/QuantExpr.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Antlr4.Runtime; +using Plang.Compiler.TypeChecker.AST.Declarations; +using Plang.Compiler.TypeChecker.Types; + +namespace Plang.Compiler.TypeChecker.AST.Expressions +{ + public class QuantExpr : IPExpr + { + public QuantExpr(ParserRuleContext sourceLocation, QuantType operation, List bound, IPExpr body, bool difference) + { + SourceLocation = sourceLocation; + Quant = operation; + Bound = bound; + Body = body; + Difference = difference; + Type = PrimitiveType.Bool; + } + + public QuantType Quant { get; } + public List Bound { get; } + public IPExpr Body { get; } + public bool Difference { get; } + + public ParserRuleContext SourceLocation { get; } + + public PLanguageType Type { get; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/QuantType.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/QuantType.cs new file mode 100644 index 0000000000..3a00765fda --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/QuantType.cs @@ -0,0 +1,10 @@ +using System; + +namespace Plang.Compiler.TypeChecker.AST.Expressions +{ + public enum QuantType + { + Forall, + Exists, + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/SentExpr.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/SentExpr.cs new file mode 100644 index 0000000000..7b31d32d63 --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/SentExpr.cs @@ -0,0 +1,23 @@ +using System.Diagnostics; +using Antlr4.Runtime; +using Plang.Compiler.TypeChecker.AST.Declarations; +using Plang.Compiler.TypeChecker.Types; + +namespace Plang.Compiler.TypeChecker.AST.Expressions +{ + public class SentExpr : IPExpr + { + public SentExpr(ParserRuleContext sourceLocation, IPExpr instance) + { + SourceLocation = sourceLocation; + Instance = instance; + Type = PrimitiveType.Bool; + } + + public IPExpr Instance { get; } + + public ParserRuleContext SourceLocation { get; } + + public PLanguageType Type { get; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/SpecAccessExpr.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/SpecAccessExpr.cs new file mode 100644 index 0000000000..845b416655 --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/SpecAccessExpr.cs @@ -0,0 +1,25 @@ +using Antlr4.Runtime; +using Plang.Compiler.TypeChecker.AST.Declarations; +using Plang.Compiler.TypeChecker.Types; + +namespace Plang.Compiler.TypeChecker.AST.Expressions +{ + public class SpecAccessExpr : IPExpr + { + public SpecAccessExpr(ParserRuleContext sourceLocation, Machine machine, IPExpr subExpr, Variable entry) + { + SourceLocation = sourceLocation; + Spec = machine; + SubExpr = subExpr; + Entry = entry; + } + + public Machine Spec { get; } + public IPExpr SubExpr { get; } + public Variable Entry { get; } + public string FieldName => Entry.Name; + + public ParserRuleContext SourceLocation { get; } + public PLanguageType Type => Entry.Type; + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/SpecRefExpr.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/SpecRefExpr.cs new file mode 100644 index 0000000000..e204d3ca50 --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/SpecRefExpr.cs @@ -0,0 +1,20 @@ +using Antlr4.Runtime; +using Plang.Compiler.TypeChecker.AST.Declarations; +using Plang.Compiler.TypeChecker.Types; + +namespace Plang.Compiler.TypeChecker.AST.Expressions +{ + public class SpecRefExpr : IStaticTerm + { + public SpecRefExpr(ParserRuleContext sourceLocation, Machine value) + { + Value = value; + SourceLocation = sourceLocation; + } + + public Machine Value { get; } + + public PLanguageType Type { get; } = PrimitiveType.Machine; + public ParserRuleContext SourceLocation { get; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/TargetsExpr.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/TargetsExpr.cs new file mode 100644 index 0000000000..36a9ffcfb5 --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/TargetsExpr.cs @@ -0,0 +1,25 @@ +using System.Diagnostics; +using Antlr4.Runtime; +using Plang.Compiler.TypeChecker.AST.Declarations; +using Plang.Compiler.TypeChecker.Types; + +namespace Plang.Compiler.TypeChecker.AST.Expressions +{ + public class TargetsExpr : IPExpr + { + public TargetsExpr(ParserRuleContext sourceLocation, IPExpr instance, IPExpr target) + { + SourceLocation = sourceLocation; + Instance = instance; + Target = target; + Type = PrimitiveType.Bool; + } + + public IPExpr Instance { get; } + public IPExpr Target { get; } + + public ParserRuleContext SourceLocation { get; } + + public PLanguageType Type { get; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/TestExpr.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/TestExpr.cs new file mode 100644 index 0000000000..63f08d7896 --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Expressions/TestExpr.cs @@ -0,0 +1,23 @@ +using Antlr4.Runtime; +using Plang.Compiler.TypeChecker.Types; + +namespace Plang.Compiler.TypeChecker.AST.Expressions +{ + public class TestExpr : IPExpr + { + public TestExpr(ParserRuleContext sourceLocation, IPExpr instance, IPDecl kind) + { + SourceLocation = sourceLocation; + Instance = instance; + Kind = kind; + Type = PrimitiveType.Bool; + } + + public IPExpr Instance { get; } + public IPDecl Kind { get; } + + public ParserRuleContext SourceLocation { get; } + + public PLanguageType Type { get; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Statements/AssumeStmt.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Statements/AssumeStmt.cs new file mode 100644 index 0000000000..cd4b2dd947 --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Statements/AssumeStmt.cs @@ -0,0 +1,19 @@ +using Antlr4.Runtime; + +namespace Plang.Compiler.TypeChecker.AST.Statements +{ + public class AssumeStmt : IPStmt + { + public AssumeStmt(ParserRuleContext sourceLocation, IPExpr assumption, IPExpr message) + { + SourceLocation = sourceLocation; + Assumption = assumption; + Message = message; + } + + public IPExpr Assumption { get; } + public IPExpr Message { get; } + + public ParserRuleContext SourceLocation { get; } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/AST/Statements/ForeachStmt.cs b/Src/PCompiler/CompilerCore/TypeChecker/AST/Statements/ForeachStmt.cs index bcda223e7a..3d318d206a 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/AST/Statements/ForeachStmt.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/AST/Statements/ForeachStmt.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Antlr4.Runtime; using Plang.Compiler.TypeChecker.AST.Declarations; @@ -5,17 +6,19 @@ namespace Plang.Compiler.TypeChecker.AST.Statements { public class ForeachStmt : IPStmt { - public ForeachStmt(ParserRuleContext sourceLocation, Variable item, IPExpr collection, IPStmt body) + public ForeachStmt(ParserRuleContext sourceLocation, Variable item, IPExpr collection, IPStmt body, List invariants) { SourceLocation = sourceLocation; Item = item; IterCollection = collection; Body = CompoundStmt.FromStatement(body); + Invariants = invariants; } public Variable Item { get; } public IPExpr IterCollection { get; } public CompoundStmt Body { get; } + public List Invariants { get; } public ParserRuleContext SourceLocation { get; } } } \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/Analyzer.cs b/Src/PCompiler/CompilerCore/TypeChecker/Analyzer.cs index 8d5ae22007..17d85ef42e 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/Analyzer.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/Analyzer.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using Antlr4.Runtime; using Antlr4.Runtime.Tree; using Plang.Compiler.TypeChecker.AST; using Plang.Compiler.TypeChecker.AST.Declarations; +using Plang.Compiler.TypeChecker.Types; namespace Plang.Compiler.TypeChecker { @@ -16,8 +18,8 @@ public static Scope AnalyzeCompilationUnit(ICompilerConfiguration config, // Step 1: Build the global scope of declarations var globalScope = BuildGlobalScope(config, programUnits); - - // Step 2: Validate machine specifications + + // Step 2a: Validate machine specifications foreach (var machine in globalScope.Machines) { @@ -32,7 +34,67 @@ public static Scope AnalyzeCompilationUnit(ICompilerConfiguration config, FunctionValidator.CheckAllPathsReturn(handler, machineFunction); } - // Step 2: Validate no static handlers + // Step 3b: for PVerifier, fill in body of Invariants, Axioms, Init conditions and Pure functions and functions with pre/post conditions + foreach (var inv in globalScope.Invariants) + { + var ctx = (PParser.InvariantDeclContext)inv.SourceLocation; + var temporaryFunction = new Function(inv.Name, inv.SourceLocation) + { + Scope = globalScope + }; + inv.Body = PopulateExpr(temporaryFunction, ctx.body, PrimitiveType.Bool, handler); + } + + foreach (var axiom in globalScope.Axioms) + { + var ctx = (PParser.AxiomDeclContext) axiom.SourceLocation; + var temporaryFunction = new Function(axiom.Name, axiom.SourceLocation) + { + Scope = globalScope + }; + axiom.Body = PopulateExpr(temporaryFunction, ctx.body, PrimitiveType.Bool, handler); + } + + foreach (var initCond in globalScope.AssumeOnStarts) + { + var ctx = (PParser.AssumeOnStartDeclContext)initCond.SourceLocation; + var temporaryFunction = new Function(initCond.Name, initCond.SourceLocation) + { + Scope = globalScope + }; + initCond.Body = PopulateExpr(temporaryFunction, ctx.body, PrimitiveType.Bool, handler); + } + + foreach (var pure in globalScope.Pures) + { + var temporaryFunction = new Function(pure.Name, pure.SourceLocation) + { + Scope = pure.Scope + }; + var context = (PParser.PureDeclContext) pure.SourceLocation; + if (context.body is not null) + { + pure.Body = PopulateExpr(temporaryFunction, context.body, pure.Signature.ReturnType, handler); + } + } + + foreach (var func in allFunctions.Where(func => func.Role.HasFlag(FunctionRole.Foreign))) + { + // populate pre/post conditions + var ctx = (PParser.ForeignFunDeclContext)func.SourceLocation; + foreach (var req in ctx._requires) + { + var preExpr = PopulateExpr(func, req, PrimitiveType.Bool, handler); + func.AddRequire(preExpr); + } + foreach (var post in ctx._ensures) + { + var postExpr = PopulateExpr(func, post, PrimitiveType.Bool, handler); + func.AddEnsure(postExpr); + } + } + + // Step 2b: Validate no static handlers foreach (var machine in globalScope.Machines) { MachineChecker.ValidateNoStaticHandlers(handler, machine); @@ -120,6 +182,17 @@ public static Scope AnalyzeCompilationUnit(ICompilerConfiguration config, return globalScope; } + private static IPExpr PopulateExpr(Function func, ParserRuleContext ctx, PLanguageType type, ITranslationErrorHandler handler) + { + var exprVisitor = new ExprVisitor(func, handler); + var body = exprVisitor.Visit(ctx); + if (!type.IsSameTypeAs(body.Type)) + { + throw handler.TypeMismatch(ctx, body.Type, type); + } + return body; + } + private static Propagation CreatePropagation(Func getter, Action setter, T value) { @@ -181,6 +254,12 @@ private static Scope BuildGlobalScope(ICompilerConfiguration config, PParser.Pro DeclarationVisitor.PopulateDeclarations(config.Handler, globalScope, programUnit, nodesToDeclarations); } + // Step 3: fill in proof blocks + foreach (var proofBlock in globalScope.ProofBlocks) + { + ProofBlockVisitor.PopulateProofBlocks(config.Handler, globalScope, proofBlock.SourceLocation, nodesToDeclarations); + } + return globalScope; } diff --git a/Src/PCompiler/CompilerCore/TypeChecker/ControlFlowChecker.cs b/Src/PCompiler/CompilerCore/TypeChecker/ControlFlowChecker.cs index ea09002934..75324dfbeb 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/ControlFlowChecker.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/ControlFlowChecker.cs @@ -71,6 +71,7 @@ private void CheckStmt(IPStmt stmt) case AddStmt _: case AnnounceStmt _: case AssertStmt _: + case AssumeStmt _: case AssignStmt _: case CtorStmt _: case FunCallStmt _: diff --git a/Src/PCompiler/CompilerCore/TypeChecker/DeclarationStubVisitor.cs b/Src/PCompiler/CompilerCore/TypeChecker/DeclarationStubVisitor.cs index baaee6c7dc..e4ce65be1a 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/DeclarationStubVisitor.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/DeclarationStubVisitor.cs @@ -1,3 +1,5 @@ +using System; +using System.Linq; using Antlr4.Runtime.Misc; using Antlr4.Runtime.Tree; using Plang.Compiler.TypeChecker.AST; @@ -45,6 +47,70 @@ public override object VisitGlobalParamDecl(PParser.GlobalParamDeclContext conte #endregion GlobalParams + public override object VisitInvariantDecl(PParser.InvariantDeclContext context) + { + var group = context.Parent as PParser.InvariantGroupDeclContext; + var name = group != null ? $"{group.name.GetText()}_{context.name.GetText()}": context.name.GetText(); + var decl = CurrentScope.Put(name, context); + nodesToDeclarations.Put(context, decl); + return null; + } + + public override object VisitAxiomDecl(PParser.AxiomDeclContext context) + { + var name = $"axiom{CurrentScope.Axioms.Count()}"; + var decl = CurrentScope.Put(name, context); + nodesToDeclarations.Put(context, decl); + return null; + } + + public override object VisitInvariantGroupDecl(PParser.InvariantGroupDeclContext context) + { + var name = context.name.GetText(); + var decl = CurrentScope.Put(name, context); + nodesToDeclarations.Put(context, decl); + context.invariantDecl().Select(Visit).ToList(); + return null; + } + + public override object VisitProofBlock(PParser.ProofBlockContext context) + { + + var name = context.name == null ? $"ProofBlock_{CurrentScope.ProofBlocks.Count()}" : context.name.GetText(); + var decl = CurrentScope.Put(name, context); + nodesToDeclarations.Put(context, decl); + context.proofBody().proofItem().Select(Visit).ToList(); + return null; + } + + public override object VisitProveUsingCmd(PParser.ProveUsingCmdContext context) + { + var name = string.Join(", ", context._targets.Select(t => t.GetText())); + if (context._premises.Count > 0) + { + name += " using " + string.Join(", ", context._premises.Select(t => t.GetText())); + } + var decl = CurrentScope.Put(name, context); + nodesToDeclarations.Put(context, decl); + return null; + } + + public override object VisitAssumeOnStartDecl(PParser.AssumeOnStartDeclContext context) + { + var name = $"init{CurrentScope.AssumeOnStarts.Count()}"; + var decl = CurrentScope.Put(name, context); + nodesToDeclarations.Put(context, decl); + return null; + } + + public override object VisitPureDecl(PParser.PureDeclContext context) + { + var name = context.name.GetText(); + var decl = CurrentScope.Put(name, context); + nodesToDeclarations.Put(context, decl); + return VisitChildrenWithNewScope(decl, context); + } + #region Events public override object VisitEventDecl(PParser.EventDeclContext context) @@ -280,6 +346,10 @@ public override object VisitBinExpr(PParser.BinExprContext context) { return null; } + public override object VisitQuantExpr(PParser.QuantExprContext context) + { + return null; + } public override object VisitUnaryExpr(PParser.UnaryExprContext context) { @@ -395,6 +465,10 @@ public override object VisitAssertStmt(PParser.AssertStmtContext context) { return null; } + public override object VisitAssumeStmt(PParser.AssumeStmtContext context) + { + return null; + } public override object VisitReturnStmt(PParser.ReturnStmtContext context) { diff --git a/Src/PCompiler/CompilerCore/TypeChecker/DeclarationVisitor.cs b/Src/PCompiler/CompilerCore/TypeChecker/DeclarationVisitor.cs index 83c15115aa..5556803f01 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/DeclarationVisitor.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/DeclarationVisitor.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Antlr4.Runtime; using Antlr4.Runtime.Tree; using Plang.Compiler.TypeChecker.AST; using Plang.Compiler.TypeChecker.AST.Declarations; +using Plang.Compiler.TypeChecker.AST.Expressions; using Plang.Compiler.TypeChecker.AST.States; using Plang.Compiler.TypeChecker.Types; using Plang.Compiler.Util; @@ -79,14 +81,7 @@ public override object VisitGlobalParamDecl(PParser.GlobalParamDeclContext conte public override object VisitEventDecl(PParser.EventDeclContext context) { // EVENT name=Iden - var pEvent = (Event) nodesToDeclarations.Get(context); - - // cardinality? - var hasAssume = context.cardinality()?.ASSUME() != null; - var hasAssert = context.cardinality()?.ASSERT() != null; - var cardinality = int.Parse(context.cardinality()?.IntLiteral().GetText() ?? "-1"); - pEvent.Assume = hasAssume ? cardinality : -1; - pEvent.Assert = hasAssert ? cardinality : -1; + var pEvent = (Event)nodesToDeclarations.Get(context); // (COLON type)? pEvent.PayloadType = ResolveType(context.type()); @@ -295,14 +290,6 @@ public override object VisitImplMachineDecl(PParser.ImplMachineDeclContext conte // MACHINE name=iden var machine = (Machine) nodesToDeclarations.Get(context); - // cardinality? - var hasAssume = context.cardinality()?.ASSUME() != null; - var hasAssert = context.cardinality()?.ASSERT() != null; - var cardinality = long.Parse(context.cardinality()?.IntLiteral().GetText() ?? "-1"); - if (cardinality > uint.MaxValue) throw Handler.ValueOutOfRange(context.cardinality(), "uint32"); - machine.Assume = hasAssume ? (uint?) cardinality : null; - machine.Assert = hasAssert ? (uint?) cardinality : null; - // receivesSends* foreach (var receivesSends in context.receivesSends()) { @@ -635,7 +622,69 @@ public override object VisitOnEventGotoState(PParser.OnEventGotoStateContext con } #endregion + + public override object VisitPureDecl(PParser.PureDeclContext context) + { + // PURE name=Iden body=Expr + var pure = (Pure) nodesToDeclarations.Get(context); + + // LPAREN funParamList? RPAREN + var paramList = context.funParamList() != null + ? (Variable[]) Visit(context.funParamList()) + : new Variable[0]; + pure.Signature.Parameters.AddRange(paramList); + + var temporaryFunction = new Function(pure.Name, context) + { + Scope = CurrentScope.MakeChildScope() + }; + + foreach (var p in paramList) + { + var param = temporaryFunction.Scope.Put(p.Name, p.SourceLocation, VariableRole.Param); + param.Type = p.Type; + nodesToDeclarations.Put(p.SourceLocation, param); + temporaryFunction.Signature.Parameters.Add(param); + } + pure.Scope = temporaryFunction.Scope; + + // (COLON type)? + pure.Signature.ReturnType = ResolveType(context.type()); + + // body will be handled in a later stage + return pure; + } + + public override object VisitInvariantDecl(PParser.InvariantDeclContext context) + { + // INVARIANT name=Iden body=Expr + var inv = (Invariant) nodesToDeclarations.Get(context); + return inv; + } + + public override object VisitAxiomDecl(PParser.AxiomDeclContext context) + { + // Axiom body=Expr + var inv = (Axiom) nodesToDeclarations.Get(context); + return inv; + } + + public override object VisitInvariantGroupDecl(PParser.InvariantGroupDeclContext context) + { + var invGroup = (InvariantGroup) nodesToDeclarations.Get(context); + invGroup.Invariants = context.invariantDecl().Select(Visit).Cast().ToList(); + return invGroup; + } + + public override object VisitAssumeOnStartDecl(PParser.AssumeOnStartDeclContext context) + { + // assume on start: body=Expr + var assume = (AssumeOnStart) nodesToDeclarations.Get(context); + // body will be handled in a later stage + return assume; + } + #region Functions public override object VisitPFunDecl(PParser.PFunDeclContext context) @@ -683,6 +732,30 @@ public override object VisitForeignFunDecl(PParser.ForeignFunDeclContext context else throw Handler.MissingDeclaration(createdInterface, "interface", createdInterface.GetText()); } + + + var temporaryFunction = new Function(fun.Name, context); + temporaryFunction.Scope = fun.Scope.MakeChildScope(); + + // (RETURN LPAREN funParam RPAREN SEMI)? + if (context.funParam() != null) + { + Variable p = (Variable)Visit(context.funParam()); + // Add the return variable to the scope so that contracts can refer to it + var ret = temporaryFunction.Scope.Put(p.Name, p.SourceLocation, VariableRole.Param); + ret.Type = p.Type; + nodesToDeclarations.Put(p.SourceLocation, ret); + temporaryFunction.Signature.Parameters.Add(ret); + + fun.ReturnVariable = ret; + // update the return type to match + fun.Signature.ReturnType = fun.ReturnVariable.Type; + } + + var exprVisitor = new ExprVisitor(temporaryFunction, Handler); + + // pre/post conditions are handled in a later phase + return fun; } diff --git a/Src/PCompiler/CompilerCore/TypeChecker/ExprVisitor.cs b/Src/PCompiler/CompilerCore/TypeChecker/ExprVisitor.cs index 63a23fa358..701e401386 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/ExprVisitor.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/ExprVisitor.cs @@ -6,6 +6,7 @@ using Plang.Compiler.TypeChecker.AST; using Plang.Compiler.TypeChecker.AST.Declarations; using Plang.Compiler.TypeChecker.AST.Expressions; +using Plang.Compiler.TypeChecker.AST.States; using Plang.Compiler.TypeChecker.Types; namespace Plang.Compiler.TypeChecker @@ -14,7 +15,7 @@ public class ExprVisitor : PParserBaseVisitor { private readonly ITranslationErrorHandler handler; private readonly Function method; - private readonly Scope table; + private Scope table; public ExprVisitor(Function method, ITranslationErrorHandler handler) { @@ -51,18 +52,82 @@ public override IPExpr VisitParenExpr(PParser.ParenExprContext context) public override IPExpr VisitNamedTupleAccessExpr(PParser.NamedTupleAccessExprContext context) { - var subExpr = Visit(context.expr()); - if (!(subExpr.Type.Canonicalize() is NamedTupleType tuple)) - { - throw handler.TypeMismatch(subExpr, TypeKind.NamedTuple); - } + IPExpr subExpr = Visit(context.expr()); var fieldName = context.field.GetText(); - if (!tuple.LookupEntry(fieldName, out var entry)) + + switch (subExpr.Type.Canonicalize()) { - throw handler.MissingNamedTupleEntry(context.field, tuple); - } + case NamedTupleType tuple: + if (!tuple.LookupEntry(fieldName, out var entry)) + { + throw handler.MissingNamedTupleEntry(context.field, tuple); + } - return new NamedTupleAccessExpr(context, subExpr, entry); + return new NamedTupleAccessExpr(context, subExpr, entry); + + case PermissionType {Origin: Machine} permission: + var machine = (Machine) permission.Origin; + + if (!machine.LookupEntry(fieldName, out var field)) + { + throw handler.MissingMachineField(context.field, machine); + } + return new MachineAccessExpr(context, machine, subExpr, field); + + case PermissionType {Origin: Interface} permission: + var pname = permission.Origin.Name; + + if (!table.Lookup(pname, out Machine m)) + { + throw handler.TypeMismatch(subExpr, [TypeKind.NamedTuple, TypeKind.Base]); + } + + if (!m.LookupEntry(fieldName, out var mfield)) + { + throw handler.MissingMachineField(context.field, m); + } + return new MachineAccessExpr(context, m, subExpr, mfield); + + case PermissionType {Origin: NamedEventSet} permission: + + var pevents = ((NamedEventSet)permission.Origin).Events.ToList(); + + foreach (var pevent in pevents) + { + switch (pevent.PayloadType.Canonicalize()) + { + case NamedTupleType namedTupleType: + if (namedTupleType.LookupEntry(fieldName, out var pentry)) + { + return new EventAccessExpr(context, pevent, subExpr, pentry); + } + break; + } + } + + throw handler.MissingEventField(context.field, pevents.First()); + + case PrimitiveType pt when pt.IsSameTypeAs(PrimitiveType.Machine): + Machine spec; + + switch (subExpr) + { + case SpecRefExpr specRefExpr: + spec = specRefExpr.Value; + break; + default: + throw handler.TypeMismatch(subExpr, [TypeKind.NamedTuple, TypeKind.Base]); + } + + if (!spec.LookupEntry(fieldName, out var sfield)) + { + throw handler.MissingMachineField(context.field, spec); + } + return new SpecAccessExpr(context, spec, subExpr, sfield); + + default: + throw handler.TypeMismatch(subExpr, [TypeKind.NamedTuple, TypeKind.Base]); + } } public override IPExpr VisitTupleAccessExpr(PParser.TupleAccessExprContext context) @@ -186,33 +251,68 @@ public override IPExpr VisitCtorExpr(PParser.CtorExprContext context) public override IPExpr VisitFunCallExpr(PParser.FunCallExprContext context) { var funName = context.fun.GetText(); - if (!table.Lookup(funName, out Function function)) + if (table.Lookup(funName, out Function function)) { - throw handler.MissingDeclaration(context.fun, "function", funName); - } + // Check the arguments + var arguments = TypeCheckingUtils.VisitRvalueList(context.rvalueList(), this).ToArray(); + ISet linearVariables = new System.Collections.Generic.HashSet(); - // Check the arguments - var arguments = TypeCheckingUtils.VisitRvalueList(context.rvalueList(), this).ToArray(); - ISet linearVariables = new HashSet(); + if (function.Signature.Parameters.Count != arguments.Length) + { + throw handler.IncorrectArgumentCount(context, arguments.Length, function.Signature.Parameters.Count); + } - if (function.Signature.Parameters.Count != arguments.Length) - { - throw handler.IncorrectArgumentCount(context, arguments.Length, function.Signature.Parameters.Count); - } + for (var i = 0; i < arguments.Length; i++) + { + var argument = arguments[i]; + var paramType = function.Signature.Parameters[i].Type; + if (!paramType.IsAssignableFrom(argument.Type)) + { + throw handler.TypeMismatch(context.rvalueList().rvalue(i), argument.Type, paramType); + } - for (var i = 0; i < arguments.Length; i++) + } + + method.AddCallee(function); + return new FunCallExpr(context, function, arguments); + } + if (table.Lookup(funName, out Pure pure)) { - var argument = arguments[i]; - var paramType = function.Signature.Parameters[i].Type; - if (!paramType.IsAssignableFrom(argument.Type)) + // Check the arguments + var arguments = TypeCheckingUtils.VisitRvalueList(context.rvalueList(), this).ToArray(); + ISet linearVariables = new System.Collections.Generic.HashSet(); + + if (pure.Signature.Parameters.Count != arguments.Length) { - throw handler.TypeMismatch(context.rvalueList().rvalue(i), argument.Type, paramType); + throw handler.IncorrectArgumentCount(context, arguments.Length, pure.Signature.Parameters.Count); } + for (var i = 0; i < arguments.Length; i++) + { + var argument = arguments[i]; + var paramType = pure.Signature.Parameters[i].Type; + if (!paramType.IsAssignableFrom(argument.Type)) + { + switch (paramType) + { + case PrimitiveType pt when pt.IsSameTypeAs(PrimitiveType.Event): + switch (argument.Type) + { + case PermissionType {Origin: NamedEventSet} per when ((NamedEventSet)(per.Origin)).Events.Count() == 1: + continue; + } + break; + } + throw handler.TypeMismatch(context.rvalueList().rvalue(i), argument.Type, paramType); + } + + } + + return new PureCallExpr(context, pure, arguments); } + + throw handler.MissingDeclaration(context.fun, "function", funName); - method.AddCallee(function); - return new FunCallExpr(context, function, arguments); } public override IPExpr VisitUnaryExpr(PParser.UnaryExprContext context) @@ -246,6 +346,100 @@ public override IPExpr VisitUnaryExpr(PParser.UnaryExprContext context) } } + public override IPExpr VisitQuantExpr(PParser.QuantExprContext context) + { + var oldTable = table; + table = table.MakeChildScope(); + + bool diff = context.diff != null; + + var bound = context.bound.funParam().Select(p => + { + var symbolName = p.name.GetText(); + var param = table.Put(symbolName, p, VariableRole.Param); + param.Type = TypeResolver.ResolveType(p.type(), table, handler); + return param; + }).Cast().ToArray(); + + if (diff && bound.ToList().Count != 1) + { + // we have the "new" annotation so the bound must be a single thing and it must be an event + throw handler.InternalError(context, new ArgumentException($"Difference quantifiers must have exactly one bound variable", nameof(context))); + } + + if (diff) + { + switch (bound[0].Type.Canonicalize()) + { + case PrimitiveType pt when pt.IsSameTypeAs(PrimitiveType.Event): + break; + case PermissionType {Origin: NamedEventSet} _: + break; + default: + throw handler.TypeMismatch(context.bound, bound[0].Type, PrimitiveType.Event); + } + } + + var body = Visit(context.body); + + table = oldTable; + + if (context.quant.Text == "forall") + { + return new QuantExpr(context, QuantType.Forall, bound.ToList(), body, diff); + } + + return new QuantExpr(context, QuantType.Exists, bound.ToList(), body, diff); + } + + public override IPExpr VisitTestExpr(PParser.TestExprContext context) + { + var instance = Visit(context.instance); + string name = context.kind.GetText(); + + if (table.Lookup(name, out Machine m)) + { + return new TestExpr(context, instance, m); + } + + if (table.Lookup(name, out Event e)) + { + return new TestExpr(context, instance, e); + } + + if (table.Lookup(name, out State s)) + { + return new TestExpr(context, instance, s); + } + + throw handler.MissingDeclaration(context, "machine, event, or state", name); + } + + public override IPExpr VisitTargetsExpr(PParser.TargetsExprContext context) + { + var instance = Visit(context.instance); + var target = Visit(context.target); + + // TODO: type check to make sure instance is an event and machine is a machine + return new TargetsExpr(context, instance, target); + } + + public override IPExpr VisitFlyingExpr(PParser.FlyingExprContext context) + { + var instance = Visit(context.instance); + + // TODO: type check to make sure instance is an event + return new FlyingExpr(context, instance); + } + + public override IPExpr VisitSentExpr(PParser.SentExprContext context) + { + var instance = Visit(context.instance); + + // TODO: type check to make sure instance is an event + return new SentExpr(context, instance); + } + public override IPExpr VisitBinExpr(PParser.BinExprContext context) { var lhs = Visit(context.lhs); @@ -268,7 +462,9 @@ public override IPExpr VisitBinExpr(PParser.BinExprContext context) var logicCtors = new Dictionary> { {"&&", (elhs, erhs) => new BinOpExpr(context, BinOpType.And, elhs, erhs)}, - {"||", (elhs, erhs) => new BinOpExpr(context, BinOpType.Or, elhs, erhs)} + {"||", (elhs, erhs) => new BinOpExpr(context, BinOpType.Or, elhs, erhs)}, + {"==>", (elhs, erhs) => new BinOpExpr(context, BinOpType.Then, elhs, erhs)}, + {"<==>", (elhs, erhs) => new BinOpExpr(context, BinOpType.Iff, elhs, erhs)} }; var compCtors = new Dictionary> @@ -355,7 +551,9 @@ public override IPExpr VisitBinExpr(PParser.BinExprContext context) return compCtors[op](lhs, rhs); case "&&": - case "||": + case "||": + case "==>": + case "<==>": if (!PrimitiveType.Bool.IsAssignableFrom(lhs.Type)) { throw handler.TypeMismatch(context.lhs, lhs.Type, PrimitiveType.Bool); @@ -522,6 +720,23 @@ public override IPExpr VisitPrimitive(PParser.PrimitiveContext context) { return new EventRefExpr(context, evt); } + + if (table.Lookup(symbolName, out Machine mac) && mac.IsSpec) + { + return new SpecRefExpr(context, mac); + } + + if (table.Lookup(symbolName, out Invariant inv)) + { + return new InvariantRefExpr(inv, context); + } + + if (table.Lookup(symbolName, out InvariantGroup invGroup)) + { + return new InvariantGroupRefExpr(invGroup, context); + } + + throw handler.MissingDeclaration(context.iden(), "variable, enum element, spec machine, or event", symbolName); throw handler.MissingDeclaration(context.iden(), "variable, enum element, or event", symbolName); } @@ -604,7 +819,7 @@ public override IPExpr VisitNamedTupleBody(PParser.NamedTupleBodyContext context var fields = context._values.Select(Visit).ToArray(); var entries = new NamedTupleEntry[fields.Length]; - var names = new HashSet(); + var names = new System.Collections.Generic.HashSet(); for (var i = 0; i < fields.Length; i++) { var entryName = context._names[i].GetText(); diff --git a/Src/PCompiler/CompilerCore/TypeChecker/FunctionValidator.cs b/Src/PCompiler/CompilerCore/TypeChecker/FunctionValidator.cs index 16b1204a1a..5a765e030e 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/FunctionValidator.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/FunctionValidator.cs @@ -40,6 +40,10 @@ public static bool SurelyReturns(IPStmt stmt) when (assertStmt.Assertion as BoolLiteralExpr)?.Value == false: return true; + case AssumeStmt assumeStmt + when (assumeStmt.Assumption as BoolLiteralExpr)?.Value == false: + return true; + case GotoStmt _: return true; diff --git a/Src/PCompiler/CompilerCore/TypeChecker/InferMachineCreates.cs b/Src/PCompiler/CompilerCore/TypeChecker/InferMachineCreates.cs index a3283248f4..316794d2f8 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/InferMachineCreates.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/InferMachineCreates.cs @@ -52,6 +52,10 @@ private static IEnumerable InferCreates(IPAST tree, ITranslationError case AssertStmt assertStmt: return InferCreatesForExpr(assertStmt.Assertion, handler) .Union(InferCreatesForExpr(assertStmt.Message, handler)); + + case AssumeStmt assumeStmt: + return InferCreatesForExpr(assumeStmt.Assumption, handler) + .Union(InferCreatesForExpr(assumeStmt.Message, handler)); case AssignStmt assignStmt: return InferCreatesForExpr(assignStmt.Location, handler) diff --git a/Src/PCompiler/CompilerCore/TypeChecker/ModuleSystemTypeChecker.cs b/Src/PCompiler/CompilerCore/TypeChecker/ModuleSystemTypeChecker.cs index 7ee7cdcdf3..ea85b509e8 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/ModuleSystemTypeChecker.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/ModuleSystemTypeChecker.cs @@ -130,7 +130,7 @@ private void CheckWellFormedness(AssertModuleExpr assertExpr) currentModule.InterfaceDef.Add(ipItem.Key, ipItem.Value); } } - + internal void CheckRefinementTest(RefinementTest test) { //check that the test module is closed with respect to creates diff --git a/Src/PCompiler/CompilerCore/TypeChecker/ProofBlockVisitor.cs b/Src/PCompiler/CompilerCore/TypeChecker/ProofBlockVisitor.cs new file mode 100644 index 0000000000..a3310d6ce7 --- /dev/null +++ b/Src/PCompiler/CompilerCore/TypeChecker/ProofBlockVisitor.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Antlr4.Runtime; +using Antlr4.Runtime.Tree; +using Plang.Compiler; +using Plang.Compiler.TypeChecker; +using Plang.Compiler.TypeChecker.AST; +using Plang.Compiler.TypeChecker.AST.Declarations; +using Plang.Compiler.TypeChecker.AST.Expressions; +using Plang.Compiler.TypeChecker.Types; + +namespace Plang.Compiler.TypeChecker +{ + public class ProofBlockVisitor : PParserBaseVisitor + { + + private ITranslationErrorHandler Handler; + private Scope CurrentScope; + private ParseTreeProperty nodesToDeclarations; + + private ProofBlockVisitor(ITranslationErrorHandler handler, Scope scope, ParseTreeProperty nodesToDeclarations) + { + Handler = handler; + CurrentScope = scope; + this.nodesToDeclarations = nodesToDeclarations; + } + + public static void PopulateProofBlocks(ITranslationErrorHandler handler, + Scope topLevelScope, + ParserRuleContext context, + ParseTreeProperty nodesToDeclarations) + { + var visitor = new ProofBlockVisitor(handler, topLevelScope, nodesToDeclarations); + visitor.Visit(context); + } + + private List ToInvariant(IPExpr e, ParserRuleContext context) + { + if (e is InvariantGroupRefExpr invGroupRef) return invGroupRef.Invariants; + if (e is InvariantRefExpr invRef) return [invRef.Invariant]; + if (!PrimitiveType.Bool.IsSameTypeAs(e.Type.Canonicalize())) + { + throw Handler.TypeMismatch(context, e.Type, PrimitiveType.Bool); + } + Invariant inv = new Invariant($"tmp_inv_{Guid.NewGuid()}", e, context); + return [inv]; + } + + public override object VisitProofBlock(PParser.ProofBlockContext context) + { + var proofBlock = (ProofBlock)nodesToDeclarations.Get(context); + proofBlock.Commands = context.proofBody().proofItem().Select(Visit).Cast().ToList(); + proofBlock.Commands.ForEach(x => x.ProofBlock = proofBlock.Name); + return proofBlock; + } + + public override object VisitProveUsingCmd(PParser.ProveUsingCmdContext context) + { + var proofCmd = (ProofCommand)nodesToDeclarations.Get(context); + var temporaryFunction = new Function(proofCmd.Name, context); + temporaryFunction.Scope = CurrentScope.MakeChildScope(); + var exprVisitor = new ExprVisitor(temporaryFunction, Handler); + List premises = []; + List goals = []; + List excepts = context._excludes.Select(exprVisitor.Visit).ToList(); + if (context.premisesAll == null) + { + premises = context._premises.Select(exprVisitor.Visit).ToList(); + } + else + { + premises = CurrentScope.AllDecls.OfType().Select(x => (IPExpr)new InvariantRefExpr(x, context)).ToList(); + } + + if (context.goalsAll == null && context.goalsDefault == null) + { + goals = context._targets.Select(exprVisitor.Visit).ToList(); + } + else if (context.goalsDefault != null) + { + goals = [new InvariantRefExpr(new Invariant(context), context)]; + } + else + { + goals = CurrentScope.AllDecls.OfType().Select(x => (IPExpr)new InvariantRefExpr(x, context)).ToList(); + } + + if (premises.Count == context._premises.Count) + { + proofCmd.Premises = premises.Zip(context._premises, (x, y) => ToInvariant(x, y)).SelectMany(x => x).ToList(); + } + else + { + proofCmd.Premises = premises.SelectMany(x => ToInvariant(x, context)).ToList(); + } + + if (goals.Count == context._targets.Count) + { + proofCmd.Goals = goals.Zip(context._targets, (x, y) => ToInvariant(x, y)).SelectMany(x => x).ToList(); + } + else + { + proofCmd.Goals = goals.SelectMany(x => ToInvariant(x, context)).ToList(); + } + + proofCmd.Excepts = excepts.Zip(context._excludes, (x, y) => ToInvariant(x, y)).SelectMany(x => x).ToList(); + proofCmd.Premises = proofCmd.Premises.Except(proofCmd.Excepts).ToList(); + proofCmd.Goals = proofCmd.Goals.Except(proofCmd.Excepts).ToList(); + + // prove A using B, ..., C means A -> B, ..., A -> C + // If there is a cycle in the graph formed by all prove-using commands, then we should throw an error. + // We could do this incrementally but the number of prove-using commands will probably be very small anyway + // so we are just going to do a topological sort every time (https://gist.github.com/Sup3rc4l1fr4g1l1571c3xp14l1d0c10u5/3341dba6a53d7171fe3397d13d00ee3f) + // TODO: using _ to pick out sub invariants? + var nodes = new HashSet(); + var edges = new HashSet<(string, string)>(); + foreach (var cmd in CurrentScope.ProofCommands) + { + if (cmd.Goals is null) continue; + foreach (var source in cmd.Goals.Select(inv => inv.Name)) + { + if (cmd.Premises is null) continue; + foreach (var target in cmd.Premises.Select(inv => inv.Name)) + { + nodes.Add(source); + nodes.Add(target); + edges.Add((source, target)); + } + } + } + + // Set of all nodes with no incoming edges + var S = new System.Collections.Generic.HashSet(nodes.Where(n => edges.All(e => e.Item2.Equals(n) == false))); + + // while S is non-empty do + while (S.Any()) + { + + // remove a node n from S + var n = S.First(); + S.Remove(n); + + // for each node m with an edge e from n to m do + foreach (var e in edges.Where(e => e.Item1.Equals(n)).ToList()) + { + var m = e.Item2; + + // remove edge e from the graph + edges.Remove(e); + + // if m has no other incoming edges then + if (edges.All(me => me.Item2.Equals(m) == false)) + { + S.Add(m); + } + } + } + + // if graph has edges then + if (edges.Any()) + { + throw Handler.CyclicProof(proofCmd.SourceLocation, proofCmd); + } + + return proofCmd; + } + } +} \ No newline at end of file diff --git a/Src/PCompiler/CompilerCore/TypeChecker/Scope.cs b/Src/PCompiler/CompilerCore/TypeChecker/Scope.cs index 16f6404bf7..3f2384d814 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/Scope.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/Scope.cs @@ -23,6 +23,13 @@ public class Scope private readonly IDictionary events = new Dictionary(); private readonly IDictionary eventSets = new Dictionary(); private readonly IDictionary functions = new Dictionary(); + private readonly IDictionary pures = new Dictionary(); + private readonly IDictionary invariants = new Dictionary(); + private readonly IDictionary axioms = new Dictionary(); + private readonly List<(string, ProofCommand)> proofCommands = new List<(string, ProofCommand)>(); + private readonly IDictionary proofBlocks = new Dictionary(); + private readonly IDictionary invariantGroups = new Dictionary(); + private readonly IDictionary assumeOnStarts = new Dictionary(); private readonly ICompilerConfiguration config; private readonly IDictionary implementations = new Dictionary(); private readonly IDictionary interfaces = new Dictionary(); @@ -57,6 +64,10 @@ private Scope(ICompilerConfiguration config, Scope parent = null) .Concat(Events) .Concat(EventSets) .Concat(Functions) + .Concat(Invariants) + .Concat(Axioms) + .Concat(AssumeOnStarts) + .Concat(Pures) .Concat(Interfaces) .Concat(Machines) .Concat(States) @@ -72,6 +83,10 @@ private Scope(ICompilerConfiguration config, Scope parent = null) public IEnumerable Events => events.Values; public IEnumerable EventSets => eventSets.Values; public IEnumerable Functions => functions.Values; + public IEnumerable Invariants => invariants.Values; + public IEnumerable Axioms => axioms.Values; + public IEnumerable AssumeOnStarts => assumeOnStarts.Values; + public IEnumerable Pures => pures.Values; public IEnumerable Interfaces => interfaces.Values; public IEnumerable Machines => machines.Values; public IEnumerable States => states.Values; @@ -82,6 +97,9 @@ private Scope(ICompilerConfiguration config, Scope parent = null) public IEnumerable RefinementTests => refinementTests.Values; public IEnumerable Implementations => implementations.Values; public IEnumerable NamedModules => namedModules.Values; + public IEnumerable ProofBlocks => proofBlocks.Values; + public IEnumerable ProofCommands => proofCommands.Select(p => p.Item2); + public IEnumerable InvariantGroups => invariantGroups.Values; public static Scope CreateGlobalScope(ICompilerConfiguration config) { @@ -161,6 +179,42 @@ public bool Get(string name, out Function tree) { return functions.TryGetValue(name, out tree); } + + public bool Get(string name, out Invariant tree) + { + return invariants.TryGetValue(name, out tree); + } + + public bool Get(string name, out InvariantGroup tree) + { + return invariantGroups.TryGetValue(name, out tree); + } + + public bool Get(string name, out Axiom tree) + { + return axioms.TryGetValue(name, out tree); + } + + public bool Get(string name, out ProofBlock pb) + { + return proofBlocks.TryGetValue(name, out pb); + } + + public bool Get(string name, out ProofCommand tree) + { + tree = proofCommands.Find(x => x.Item1 == name).Item2; + return tree != null; + } + + public bool Get(string name, out AssumeOnStart tree) + { + return assumeOnStarts.TryGetValue(name, out tree); + } + + public bool Get(string name, out Pure tree) + { + return pures.TryGetValue(name, out tree); + } public bool Get(string name, out Interface tree) { @@ -295,7 +349,92 @@ public bool Lookup(string name, out Function tree) tree = null; return false; } + + public bool Lookup(string name, out Invariant tree) + { + var current = this; + while (current != null) + { + if (current.Get(name, out tree)) + { + return true; + } + + current = current.Parent; + } + + tree = null; + return false; + } + + public bool Lookup(string name, out InvariantGroup tree) + { + var current = this; + while (current != null) + { + if (current.Get(name, out tree)) + { + return true; + } + + current = current.Parent; + } + + tree = null; + return false; + } + + public bool Lookup(string name, out Axiom tree) + { + var current = this; + while (current != null) + { + if (current.Get(name, out tree)) + { + return true; + } + + current = current.Parent; + } + + tree = null; + return false; + } + + public bool Lookup(string name, out ProofCommand tree) + { + var current = this; + while (current != null) + { + if (current.Get(name, out tree)) + { + return true; + } + + current = current.Parent; + } + + tree = null; + return false; + } + + public bool Lookup(string name, out Pure tree) + { + var current = this; + while (current != null) + { + if (current.Get(name, out tree)) + { + return true; + } + + current = current.Parent; + } + tree = null; + return false; + } + public bool Lookup(string name, out Interface tree) { var current = this; @@ -340,6 +479,16 @@ public bool Lookup(string name, out State tree) return true; } + // look inside machines to find the state. + // TODO: bug if multiple machines have the same state name + foreach (var m in current.Machines) + { + if (m.Scope.Get(name, out tree)) + { + return true; + } + } + current = current.Parent; } @@ -549,6 +698,62 @@ public Function Put(string name, PParser.FunDeclContext tree) return function; } + + public Pure Put(string name, PParser.PureDeclContext tree) + { + var pure = new Pure(name, tree); + CheckConflicts(pure, Namespace(pures)); + pures.Add(name, pure); + return pure; + } + + public Invariant Put(string name, PParser.InvariantDeclContext tree) + { + var invariant = new Invariant(name, null, tree); // need to add expr later + CheckConflicts(invariant, Namespace(invariants)); + invariants.Add(name, invariant); + return invariant; + } + + public InvariantGroup Put(string name, PParser.InvariantGroupDeclContext tree) + { + var group = new InvariantGroup(name, tree); + CheckConflicts(group, Namespace(invariantGroups)); + invariantGroups.Add(name, group); + return group; + } + + public Axiom Put(string name, PParser.AxiomDeclContext tree) + { + var axiom = new Axiom(name, null, tree); // need to add expr later + CheckConflicts(axiom, Namespace(axioms)); + axioms.Add(name, axiom); + return axiom; + } + + public ProofCommand Put(string name, PParser.ProofItemContext tree) + { + var proofCommand = new ProofCommand(name, tree); + proofCommands.Add((name, proofCommand)); + return proofCommand; + } + + public ProofBlock Put(string name, PParser.ProofBlockContext tree) + { + var proofBlock = new ProofBlock(name, tree); + CheckConflicts(proofBlock, Namespace(proofBlocks)); + proofBlocks.Add(name, proofBlock); + return proofBlock; + } + + public AssumeOnStart Put(string name, PParser.AssumeOnStartDeclContext tree) + { + var assumeOnStart = new AssumeOnStart(name, null, tree); // need to add expr later + CheckConflicts(assumeOnStart, Namespace(assumeOnStarts)); + assumeOnStarts.Add(name, assumeOnStart); + return assumeOnStart; + } + public EnumElem Put(string name, PParser.EnumElemContext tree) { var enumElem = new EnumElem(name, tree); diff --git a/Src/PCompiler/CompilerCore/TypeChecker/StatementVisitor.cs b/Src/PCompiler/CompilerCore/TypeChecker/StatementVisitor.cs index 16604ee817..caa917184f 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/StatementVisitor.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/StatementVisitor.cs @@ -63,6 +63,30 @@ public override IPStmt VisitAssertStmt(PParser.AssertStmtContext context) return new AssertStmt(context, assertion, assertMessage); } + + + public override IPStmt VisitAssumeStmt(PParser.AssumeStmtContext context) + { + var assumption = exprVisitor.Visit(context.assumption); + if (!PrimitiveType.Bool.IsSameTypeAs(assumption.Type)) + { + throw handler.TypeMismatch(context.assumption, assumption.Type, PrimitiveType.Bool); + } + IPExpr assertMessage = new StringExpr(context, @$"{config.LocationResolver.GetLocation(context).ToString().Replace(@"\", @"\\")}",new List()); + if (context.message != null) + { + var message = exprVisitor.Visit(context.message); + if (!message.Type.IsSameTypeAs(PrimitiveType.String)) + { + throw handler.TypeMismatch(context.message, message.Type, PrimitiveType.String); + } + + assertMessage = new StringExpr(message.SourceLocation, "{0} {1}",new List() {assertMessage, + message}); + } + + return new AssumeStmt(context, assumption, assertMessage); + } public override IPStmt VisitPrintStmt(PParser.PrintStmtContext context) { @@ -258,9 +282,12 @@ public override IPStmt VisitForeachStmt(PParser.ForeachStmtContext context) throw handler.TypeMismatch(context.item, itemType, expectedItemType); var body = Visit(context.statement()); - return new ForeachStmt(context, var, collection, body); - } + var invs = context._invariants.Select(exprVisitor.Visit).ToList(); + + return new ForeachStmt(context, var, collection, body, invs); + } + public override IPStmt VisitIfStmt(PParser.IfStmtContext context) { var condition = exprVisitor.Visit(context.expr()); diff --git a/Src/PCompiler/CompilerCore/TypeChecker/TypeResolver.cs b/Src/PCompiler/CompilerCore/TypeChecker/TypeResolver.cs index 39b22c3681..a5d06d2d6b 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/TypeResolver.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/TypeResolver.cs @@ -93,6 +93,13 @@ public override PLanguageType VisitNamedType(PParser.NamedTypeContext context) { return new PermissionType(machine); } + + if (scope.Lookup(typeName, out Event pevent)) + { + var eset = new NamedEventSet(pevent.Name, pevent.SourceLocation); + eset.AddEvent(pevent); + return new PermissionType(eset); + } throw handler.MissingDeclaration(context.name, "enum, typedef, event set, machine, or interface", typeName); diff --git a/Src/PCompiler/CompilerCore/TypeChecker/Types/PermissionType.cs b/Src/PCompiler/CompilerCore/TypeChecker/Types/PermissionType.cs index 6c96452550..db475dddd3 100644 --- a/Src/PCompiler/CompilerCore/TypeChecker/Types/PermissionType.cs +++ b/Src/PCompiler/CompilerCore/TypeChecker/Types/PermissionType.cs @@ -8,28 +8,28 @@ namespace Plang.Compiler.TypeChecker.Types { public class PermissionType : PLanguageType { - private readonly IPDecl origin; - public PermissionType(Machine machine) : base(TypeKind.Base) { - origin = machine; + Origin = machine; AllowedPermissions = new Lazy>(() => machine.Receives.Events.ToList()); } public PermissionType(Interface pInterface) : base(TypeKind.Base) { - origin = pInterface; + Origin = pInterface; AllowedPermissions = new Lazy>(() => pInterface.ReceivableEvents.Events.ToList()); } public PermissionType(NamedEventSet eventSet) : base(TypeKind.Base) { - origin = eventSet; + Origin = eventSet; AllowedPermissions = new Lazy>(() => eventSet.Events.ToList()); } + + public IPDecl Origin { get; } - public override string OriginalRepresentation => origin.Name; - public override string CanonicalRepresentation => origin.Name; + public override string OriginalRepresentation => Origin.Name; + public override string CanonicalRepresentation => Origin.Name; public override Lazy> AllowedPermissions { get; } diff --git a/Src/PCompiler/PCommandLine/Options/PCompilerOptions.cs b/Src/PCompiler/PCommandLine/Options/PCompilerOptions.cs index b9fd5af9f5..5bc84ff472 100644 --- a/Src/PCompiler/PCommandLine/Options/PCompilerOptions.cs +++ b/Src/PCompiler/PCommandLine/Options/PCompilerOptions.cs @@ -44,6 +44,14 @@ internal PCompilerOptions() Parser.AddArgument("pobserve-package", "po", "PObserve package name").IsHidden = true; Parser.AddArgument("debug", "d", "Enable debug logs", typeof(bool)).IsHidden = true; + + var pvGroup = Parser.GetOrCreateGroup("pverifier", "PVerifier options"); + pvGroup.AddArgument("timeout", "t", "Set SMT solver timeout in seconds", typeof(int)).IsHidden = true; + + pvGroup.AddArgument("no-event-handler-checks", "nch", "Do not check that all events are handled", typeof(bool)).IsHidden = true; + pvGroup.AddArgument("proof-blocks", "pb", "List of proof blocks to check").IsMultiValue = true; + pvGroup.AddArgument("check-only", "co", "Check only the specified machine", typeof(string)).IsHidden = true; + pvGroup.AddArgument("jobs", "j", "Number of parallel processes to use", typeof(int)).IsHidden = true; } /// @@ -160,6 +168,15 @@ private static void UpdateConfigurationWithParsedArgument(CompilerConfiguration case "debug": compilerConfiguration.Debug = true; break; + case "timeout": + compilerConfiguration.Timeout = (int)option.Value; + break; + case "check-only": + compilerConfiguration.CheckOnly = (string)option.Value; + break; + case "jobs": + compilerConfiguration.Parallelism = (int)option.Value; + break; case "mode": compilerConfiguration.OutputLanguages = new List(); switch (((string)option.Value).ToLowerInvariant()) @@ -183,6 +200,15 @@ private static void UpdateConfigurationWithParsedArgument(CompilerConfiguration case "pobserve-package": compilerConfiguration.PObservePackageName = (string)option.Value; break; + case "proof-blocks": + { + var proofBlocks = (string[])option.Value; + foreach (var block in proofBlocks.Distinct()) + { + compilerConfiguration.TargetProofBlocks.Add(block); + } + break; + } case "pfiles": { var files = (string[])option.Value; diff --git a/Src/PCompiler/PCommandLine/Parser/ParsePProjectFile.cs b/Src/PCompiler/PCommandLine/Parser/ParsePProjectFile.cs index 38f6bbe3b0..20a13cdf1e 100644 --- a/Src/PCompiler/PCommandLine/Parser/ParsePProjectFile.cs +++ b/Src/PCompiler/PCommandLine/Parser/ParsePProjectFile.cs @@ -234,9 +234,12 @@ private IList GetTargetLanguages(FileInfo fullPathName) case "stately": outputLanguages.Add(CompilerOutput.Stately); break; + case "pverifier": + outputLanguages.Add(CompilerOutput.PVerifier); + break; default: throw new CommandlineParsingError( - $"Expected PChecker, PObserve, Stately, or Symbolic as target, received {projectXml.Element("Target")?.Value}"); + $"Expected PChecker, PObserve, Stately, PVerifier, or Symbolic as target, received {projectXml.Element("Target")?.Value}"); } } } diff --git a/Tst/PortfolioTests/TokenRing/TokenRing.p b/Tst/PortfolioTests/TokenRing/TokenRing.p index 3f1e370b16..dd494febd9 100644 --- a/Tst/PortfolioTests/TokenRing/TokenRing.p +++ b/Tst/PortfolioTests/TokenRing/TokenRing.p @@ -1,8 +1,8 @@ -event Empty assert 1 ; -event Sending assert 1 : machine ; -event Done assert 1 : machine ; -event Unit assert 0 ; -event Next assert 1 : machine ; +event Empty; +event Sending : machine ; +event Done : machine ; +event Unit; +event Next : machine ; event Send : machine ; event Ready ; diff --git a/Tst/RegressionTests/Combined/StaticError/ControlImpureEnclosedFunCalls/EnclosedFunCalls.p b/Tst/RegressionTests/Combined/StaticError/ControlImpureEnclosedFunCalls/EnclosedFunCalls.p index 797ef8ebac..6c2c6c5c88 100644 --- a/Tst/RegressionTests/Combined/StaticError/ControlImpureEnclosedFunCalls/EnclosedFunCalls.p +++ b/Tst/RegressionTests/Combined/StaticError/ControlImpureEnclosedFunCalls/EnclosedFunCalls.p @@ -2,11 +2,11 @@ // Cases covered: // enclosed function calls used in "do" and "goto" -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event E4 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event E4; +event unit; machine Main { var i: int; diff --git a/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit1/ControlImpureInExit.p b/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit1/ControlImpureInExit.p index 85d2fb9d18..80ab7ec59a 100644 --- a/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit1/ControlImpureInExit.p +++ b/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit1/ControlImpureInExit.p @@ -3,10 +3,10 @@ // "pop" and "raise" in "exit" function, // in both anon and named functions -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event unit; machine Main { var i: int; diff --git a/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit2/ControlImpureInExit.p b/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit2/ControlImpureInExit.p index 4bdb5e6394..6006720989 100644 --- a/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit2/ControlImpureInExit.p +++ b/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit2/ControlImpureInExit.p @@ -3,10 +3,10 @@ // "pop" and "raise" in "exit" function, // in both anon and named functions -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event unit; machine Main { var i: int; diff --git a/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit3/ControlImpureInExit.p b/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit3/ControlImpureInExit.p index bc04453776..f16876118b 100644 --- a/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit3/ControlImpureInExit.p +++ b/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit3/ControlImpureInExit.p @@ -3,10 +3,10 @@ // "pop" and "raise" in "exit" function, // in both anon and named functions -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event unit; machine Main { var i: int; diff --git a/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit4/ControlImpureInExit.p b/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit4/ControlImpureInExit.p index 3615566255..1f364b92c2 100644 --- a/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit4/ControlImpureInExit.p +++ b/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit4/ControlImpureInExit.p @@ -3,10 +3,10 @@ // "pop" and "raise" in "exit" function, // in both anon and named functions -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event unit; machine Main { var i: int; diff --git a/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit5/ControlImpureInExit.p b/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit5/ControlImpureInExit.p index bb5de3a1f1..4094633c8f 100644 --- a/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit5/ControlImpureInExit.p +++ b/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit5/ControlImpureInExit.p @@ -3,10 +3,10 @@ // "pop" and "raise" in "exit" function, // in both anon and named functions -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event unit; machine Main { var i: int; diff --git a/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit6/ControlImpureInExit.p b/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit6/ControlImpureInExit.p index d8890de045..ac05e45a6c 100644 --- a/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit6/ControlImpureInExit.p +++ b/Tst/RegressionTests/Combined/StaticError/ControlImpureInExit6/ControlImpureInExit.p @@ -3,10 +3,10 @@ // "pop" and "raise" in "exit" function, // in both anon and named functions -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event unit; machine Main { var i: int; diff --git a/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto1/ControlImpureErrorsGoto.p b/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto1/ControlImpureErrorsGoto.p index b9670de174..a1fbfe0a4b 100644 --- a/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto1/ControlImpureErrorsGoto.p +++ b/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto1/ControlImpureErrorsGoto.p @@ -3,11 +3,11 @@ // "pop" and "raise" in "goto" function, // used in anonymous functions (error) and invoked by (named) function calls (error) -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event E4 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event E4; +event unit; machine Main { var i: int; diff --git a/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto2/ControlImpureErrorsGoto.p b/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto2/ControlImpureErrorsGoto.p index fb327734a1..5b9fe47222 100644 --- a/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto2/ControlImpureErrorsGoto.p +++ b/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto2/ControlImpureErrorsGoto.p @@ -3,11 +3,11 @@ // "pop" and "raise" in "goto" function, // used in anonymous functions (error) and invoked by (named) function calls (error) -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event E4 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event E4; +event unit; machine Main { var i: int; diff --git a/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto3/ControlImpureErrorsGoto.p b/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto3/ControlImpureErrorsGoto.p index 858af91412..0d9f23d684 100644 --- a/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto3/ControlImpureErrorsGoto.p +++ b/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto3/ControlImpureErrorsGoto.p @@ -3,11 +3,11 @@ // "pop" and "raise" in "goto" function, // used in anonymous functions (error) and invoked by (named) function calls (error) -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event E4 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event E4; +event unit; machine Main { var i: int; diff --git a/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto4/ControlImpureErrorsGoto.p b/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto4/ControlImpureErrorsGoto.p index a12f60a898..16670190f7 100644 --- a/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto4/ControlImpureErrorsGoto.p +++ b/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto4/ControlImpureErrorsGoto.p @@ -3,11 +3,11 @@ // "pop" and "raise" in "goto" function, // used in anonymous functions (error) and invoked by (named) function calls (error) -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event E4 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event E4; +event unit; machine Main { var i: int; diff --git a/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto5/ControlImpureErrorsGoto.p b/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto5/ControlImpureErrorsGoto.p index 973c5f2630..92dbb62fb7 100644 --- a/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto5/ControlImpureErrorsGoto.p +++ b/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto5/ControlImpureErrorsGoto.p @@ -3,11 +3,11 @@ // "pop" and "raise" in "goto" function, // used in anonymous functions (error) and invoked by (named) function calls (error) -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event E4 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event E4; +event unit; machine Main { var i: int; diff --git a/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto6/ControlImpureErrorsGoto.p b/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto6/ControlImpureErrorsGoto.p index 8ddcc09a6c..534934ab7f 100644 --- a/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto6/ControlImpureErrorsGoto.p +++ b/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto6/ControlImpureErrorsGoto.p @@ -3,11 +3,11 @@ // "pop" and "raise" in "goto" function, // used in anonymous functions (error) and invoked by (named) function calls (error) -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event E4 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event E4; +event unit; machine Main { var i: int; diff --git a/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto7/ControlImpureErrorsGoto.p b/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto7/ControlImpureErrorsGoto.p index c4104ced1d..ad4774f6b9 100644 --- a/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto7/ControlImpureErrorsGoto.p +++ b/Tst/RegressionTests/Combined/StaticError/ControlImpureInGoto7/ControlImpureErrorsGoto.p @@ -3,11 +3,11 @@ // "pop" and "raise" in "goto" function, // used in anonymous functions (error) and invoked by (named) function calls (error) -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event E4 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event E4; +event unit; machine Main { var i: int; diff --git a/Tst/RegressionTests/Combined/StaticError/PopInExitFun/PopInExitFun.p b/Tst/RegressionTests/Combined/StaticError/PopInExitFun/PopInExitFun.p index 036fc185b5..22e23f15e2 100644 --- a/Tst/RegressionTests/Combined/StaticError/PopInExitFun/PopInExitFun.p +++ b/Tst/RegressionTests/Combined/StaticError/PopInExitFun/PopInExitFun.p @@ -2,7 +2,7 @@ // case: "pop" in exit function of the state: // PurityError(c, called) :- c is StateDecl(_, owner, _, called, _), called = AnonFunDecl(owner, _), ControlImpure(called). -event E1 assert 1; +event E1; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Combined/StaticError/RaiseInExitFun/RaiseInExitFun.p b/Tst/RegressionTests/Combined/StaticError/RaiseInExitFun/RaiseInExitFun.p index 38deb1c938..44facb1ae5 100644 --- a/Tst/RegressionTests/Combined/StaticError/RaiseInExitFun/RaiseInExitFun.p +++ b/Tst/RegressionTests/Combined/StaticError/RaiseInExitFun/RaiseInExitFun.p @@ -2,7 +2,7 @@ // case: "raise" in exit function of the state: // PurityError(c, called) :- c is StateDecl(_, owner, _, called, _), called = AnonFunDecl(owner, _), ControlImpure(called). -event E1 assert 1; +event E1; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/Correct/BugRepro1/FunctionParMutated.p b/Tst/RegressionTests/Feature1SMLevelDecls/Correct/BugRepro1/FunctionParMutated.p index b8d98b159a..60a0523989 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/Correct/BugRepro1/FunctionParMutated.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/Correct/BugRepro1/FunctionParMutated.p @@ -2,7 +2,7 @@ //the XYZ checks that function state is preserved if //function execution is interrupted by a scheduled event ("send" in this XYZ) -event Ping assert 1 : int; +event Ping : int; event Success; machine Main { diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/Correct/PingPong/PingPong.p b/Tst/RegressionTests/Feature1SMLevelDecls/Correct/PingPong/PingPong.p index 9b9bbc942d..bb9bea0666 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/Correct/PingPong/PingPong.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/Correct/PingPong/PingPong.p @@ -1,5 +1,5 @@ -event Ping assert 1: machine; -event Pong assert 2: machine; +event Ping : machine; +event Pong : machine; event Success; event M_Ping; event M_Pong; diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/DynamicError/MaxInstances/MaxInstances.p b/Tst/RegressionTests/Feature1SMLevelDecls/DynamicError/MaxInstances/MaxInstances.p index 2811429325..0ecfee2429 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/DynamicError/MaxInstances/MaxInstances.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/DynamicError/MaxInstances/MaxInstances.p @@ -1,7 +1,7 @@ // This sample XYZs assert/assume annotations on events // Number of instances greater than assumed event E0; -event E1 assume 0; +event E1; machine Main { var ghost_machine: machine; diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/DeferIgnoreSameEvent/DeferIgnore1.p b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/DeferIgnoreSameEvent/DeferIgnore1.p index 7c861d0729..c0d7c90aae 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/DeferIgnoreSameEvent/DeferIgnore1.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/DeferIgnoreSameEvent/DeferIgnore1.p @@ -1,9 +1,9 @@ // 1.6.6.1. Multiple handlers for the event: ignore/defer case // -event E1 assert 2; -event E2 assert 2; -event unit assert 1; +event E1; +event E2; +event unit; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/DeferredNullEvent/DeferredNullEvent.p b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/DeferredNullEvent/DeferredNullEvent.p index 4482ed5b7e..a069bc63b3 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/DeferredNullEvent/DeferredNullEvent.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/DeferredNullEvent/DeferredNullEvent.p @@ -1,8 +1,8 @@ // 1.7.7. "null" event cannot be deferred // (parsing error) -event E1 assert 2; -event unit assert 1; +event E1; +event unit; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/EventDeferredDoSameState/EventDeferredDoSameState.p b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/EventDeferredDoSameState/EventDeferredDoSameState.p index 9752cef9ac..faa56e28b6 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/EventDeferredDoSameState/EventDeferredDoSameState.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/EventDeferredDoSameState/EventDeferredDoSameState.p @@ -1,5 +1,5 @@ -event PING assert 1 : machine; -event PONG assert 1; +event PING : machine; +event PONG; event SUCCESS; machine Main { diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/EventDeferredHandledSameState/EventDeferredHandledSameState.p b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/EventDeferredHandledSameState/EventDeferredHandledSameState.p index 08bd414bee..a23f046683 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/EventDeferredHandledSameState/EventDeferredHandledSameState.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/EventDeferredHandledSameState/EventDeferredHandledSameState.p @@ -1,8 +1,8 @@ // P semantics XYZ: one machine, event both deferred and handled in the same state: // handler overrides -event E2 assert 2; -event E1 assert 1; +event E2; +event E1; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/EventDeferredTransDoSameState/EventDeferredTransDoSameState.p b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/EventDeferredTransDoSameState/EventDeferredTransDoSameState.p index 6d7c87994d..76311ca466 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/EventDeferredTransDoSameState/EventDeferredTransDoSameState.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/EventDeferredTransDoSameState/EventDeferredTransDoSameState.p @@ -1,7 +1,7 @@ //This is a repro for a bug in static checking (corrected since): //SUCCESS is deferred and there's a transition on SUCCESS in the same state -event PING assert 1 : machine; -event PONG assert 1; +event PING : machine; +event PONG; event SUCCESS; machine Main { diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/HasEntryArgs/entryExit_2.p b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/HasEntryArgs/entryExit_2.p index 246efb5614..aa8b902c18 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/HasEntryArgs/entryExit_2.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/HasEntryArgs/entryExit_2.p @@ -1,4 +1,4 @@ -event Empty assert 1; +event Empty; event a; event b; event c; diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/HasExitArgs/entryExit_2.p b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/HasExitArgs/entryExit_2.p index 08b561ebbd..9aed0f4de4 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/HasExitArgs/entryExit_2.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/HasExitArgs/entryExit_2.p @@ -1,4 +1,4 @@ -event Empty assert 1; +event Empty; event a; event b; event c; diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/IgnoredNullEvent/IgnoredNullEvent.p b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/IgnoredNullEvent/IgnoredNullEvent.p index 57dbf00cf0..327421ba4f 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/IgnoredNullEvent/IgnoredNullEvent.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/IgnoredNullEvent/IgnoredNullEvent.p @@ -1,8 +1,8 @@ // 1.7.7. "null" event cannot be "ignored" // (parsing error) -event E1 assert 2; -event unit assert 1; +event E1; +event unit; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentDoFun/nonExistDo.p b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentDoFun/nonExistDo.p index 4019efd982..c59e69c227 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentDoFun/nonExistDo.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentDoFun/nonExistDo.p @@ -1,4 +1,4 @@ -event Empty assert 1; +event Empty; event a; event b; event c; diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentEntryFun/entryExit_2.p b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentEntryFun/entryExit_2.p index 63f402e4cf..7460ad07e7 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentEntryFun/entryExit_2.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentEntryFun/entryExit_2.p @@ -1,4 +1,4 @@ -event Empty assert 1; +event Empty; event a; event b; event c; diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentExitFun/entryExit_2.p b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentExitFun/entryExit_2.p index 06480d4831..1f7cf058d8 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentExitFun/entryExit_2.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentExitFun/entryExit_2.p @@ -1,4 +1,4 @@ -event Empty assert 1; +event Empty; event a; event b; event c; diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentGotoFun/nonExistGoto.p b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentGotoFun/nonExistGoto.p index 0176dff752..624a8657ac 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentGotoFun/nonExistGoto.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NonExistentGotoFun/nonExistGoto.p @@ -1,4 +1,4 @@ -event Empty assert 1; +event Empty; event a; event b; event c; diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NullEventDecl/NullEventDecl.p b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NullEventDecl/NullEventDecl.p index d90e39d865..17e7316033 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NullEventDecl/NullEventDecl.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/NullEventDecl/NullEventDecl.p @@ -1,6 +1,6 @@ // 1.7.7. "null" event cannot be declared (parse error) -event null assert 1; //error +event null; //error machine Main { var XYZ: bool; //init with "false" start state Real1_Init { diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/RaisedNullEvent/RaisedNullEvent.p b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/RaisedNullEvent/RaisedNullEvent.p index 046032d871..c8bba3c62f 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/RaisedNullEvent/RaisedNullEvent.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/RaisedNullEvent/RaisedNullEvent.p @@ -1,6 +1,6 @@ // 1.7.7. "null" event cannot be raised (parse error) -event E1 assert 1; +event E1; machine Main { var XYZ: bool; //init with "false" start state Real1_Init { diff --git a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/SentNullEvent/SentNullEvent.p b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/SentNullEvent/SentNullEvent.p index 2df5065bb2..119a9f27f8 100644 --- a/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/SentNullEvent/SentNullEvent.p +++ b/Tst/RegressionTests/Feature1SMLevelDecls/StaticError/SentNullEvent/SentNullEvent.p @@ -1,6 +1,6 @@ // 1.7.7. "null" event cannot be sent (parse error) -event E1 assert 1; +event E1; machine Main { var XYZ: bool; //init with "false" start state Real1_Init { diff --git a/Tst/RegressionTests/Feature2Stmts/DynamicError/GotoStmt1/gotoStmt1.p b/Tst/RegressionTests/Feature2Stmts/DynamicError/GotoStmt1/gotoStmt1.p index bbbfd30c81..9b50954d41 100644 --- a/Tst/RegressionTests/Feature2Stmts/DynamicError/GotoStmt1/gotoStmt1.p +++ b/Tst/RegressionTests/Feature2Stmts/DynamicError/GotoStmt1/gotoStmt1.p @@ -4,8 +4,8 @@ // E2 is handled by Action2 after entering Real1_Init upon "goto" transition // Result: assert on line 21 is raised by Zing -event E2 assert 1; -event E1 assert 1; +event E2; +event E1; machine Main { start state Real1_Init { diff --git a/Tst/RegressionTests/Feature2Stmts/DynamicError/GotoStmt2/gotoStmt2.p b/Tst/RegressionTests/Feature2Stmts/DynamicError/GotoStmt2/gotoStmt2.p index d1df214f7b..d0e02b3c2f 100644 --- a/Tst/RegressionTests/Feature2Stmts/DynamicError/GotoStmt2/gotoStmt2.p +++ b/Tst/RegressionTests/Feature2Stmts/DynamicError/GotoStmt2/gotoStmt2.p @@ -1,9 +1,9 @@ // P semantics XYZ: one machine, "goto" statement, action is not inherited by the destination state // This XYZ checks that after "goto" statement, action of the src state is not inherited by the dest state -event E2 assert 1; -event E1 assert 1; -event E3 assert 1; +event E2; +event E1; +event E3; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Feature2Stmts/StaticError/entryExit/entryExit.p b/Tst/RegressionTests/Feature2Stmts/StaticError/entryExit/entryExit.p index 3ec74d0f79..2c61973410 100644 --- a/Tst/RegressionTests/Feature2Stmts/StaticError/entryExit/entryExit.p +++ b/Tst/RegressionTests/Feature2Stmts/StaticError/entryExit/entryExit.p @@ -1,4 +1,4 @@ -event Empty assert 1; +event Empty; event a; event b; event c; diff --git a/Tst/RegressionTests/Feature2Stmts/StaticError/eventExprSendRaise/EventExprSendRaise.p b/Tst/RegressionTests/Feature2Stmts/StaticError/eventExprSendRaise/EventExprSendRaise.p index 88a050d7cf..c82af4ecb5 100644 --- a/Tst/RegressionTests/Feature2Stmts/StaticError/eventExprSendRaise/EventExprSendRaise.p +++ b/Tst/RegressionTests/Feature2Stmts/StaticError/eventExprSendRaise/EventExprSendRaise.p @@ -1,7 +1,7 @@ // "raise", "send" and announce invocation with non-constant event expression -event E1 assert 1; -event E2 assert 1; +event E1; +event E2; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Feature2Stmts/StaticError/linear3/linear3.p b/Tst/RegressionTests/Feature2Stmts/StaticError/linear3/linear3.p index 82a3087cfe..43b880da4e 100644 --- a/Tst/RegressionTests/Feature2Stmts/StaticError/linear3/linear3.p +++ b/Tst/RegressionTests/Feature2Stmts/StaticError/linear3/linear3.p @@ -1,8 +1,8 @@ //Check contexts for swap and move -event E1 assert 1: int; -event E2 assert 1: int; -event E3 assert 1: int; +event E1 : int; +event E2 : int; +event E3 : int; machine Main { var x: int; diff --git a/Tst/RegressionTests/Feature3Exprs/Correct/ExprOperatorsAsserts/ExprOperatorsAsserts.p b/Tst/RegressionTests/Feature3Exprs/Correct/ExprOperatorsAsserts/ExprOperatorsAsserts.p index 54ef4ea545..59bffea095 100644 --- a/Tst/RegressionTests/Feature3Exprs/Correct/ExprOperatorsAsserts/ExprOperatorsAsserts.p +++ b/Tst/RegressionTests/Feature3Exprs/Correct/ExprOperatorsAsserts/ExprOperatorsAsserts.p @@ -3,10 +3,10 @@ //Basic types: int, bool, event //This XYZ can be further extended for combined non-atomic types -event E assert 1; -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; +event E; +event E1; +event E2; +event E3; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); diff --git a/Tst/RegressionTests/Feature3Exprs/Correct/ShortCircuitEval/ShortCircuitEval.p b/Tst/RegressionTests/Feature3Exprs/Correct/ShortCircuitEval/ShortCircuitEval.p index f03e08bca6..b9bf834bee 100644 --- a/Tst/RegressionTests/Feature3Exprs/Correct/ShortCircuitEval/ShortCircuitEval.p +++ b/Tst/RegressionTests/Feature3Exprs/Correct/ShortCircuitEval/ShortCircuitEval.p @@ -1,6 +1,6 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps //XYZs "index-out-of-bounds" error detection in Zinger and runtime -event E assert 1; +event E; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); var ts: (a: int, b: int); diff --git a/Tst/RegressionTests/Feature3Exprs/DynamicError/InOperator/InOperator.p b/Tst/RegressionTests/Feature3Exprs/DynamicError/InOperator/InOperator.p index be75e5d41c..59e4e9181b 100644 --- a/Tst/RegressionTests/Feature3Exprs/DynamicError/InOperator/InOperator.p +++ b/Tst/RegressionTests/Feature3Exprs/DynamicError/InOperator/InOperator.p @@ -1,7 +1,7 @@ -event E assert 1; -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; +event E; +event E1; +event E2; +event E3; machine Main { var sequence: seq[any]; diff --git a/Tst/RegressionTests/Feature3Exprs/StaticError/ExprsOperators/ExprOperators.p b/Tst/RegressionTests/Feature3Exprs/StaticError/ExprsOperators/ExprOperators.p index 7f6529a2cc..14ea0034f7 100644 --- a/Tst/RegressionTests/Feature3Exprs/StaticError/ExprsOperators/ExprOperators.p +++ b/Tst/RegressionTests/Feature3Exprs/StaticError/ExprsOperators/ExprOperators.p @@ -3,10 +3,10 @@ //Basic types: int, bool, event //This XYZ can be further extended for combined non-atomic types -event E assert 1; -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; +event E; +event E1; +event E2; +event E3; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); diff --git a/Tst/RegressionTests/Feature3Exprs/StaticError/InOperator/InOperator.p b/Tst/RegressionTests/Feature3Exprs/StaticError/InOperator/InOperator.p index 1559d28d5d..2d26f5f84c 100644 --- a/Tst/RegressionTests/Feature3Exprs/StaticError/InOperator/InOperator.p +++ b/Tst/RegressionTests/Feature3Exprs/StaticError/InOperator/InOperator.p @@ -1,7 +1,7 @@ -event E assert 1; -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; +event E; +event E1; +event E2; +event E3; machine Main { var sequence: seq[int]; diff --git a/Tst/RegressionTests/Feature4DataTypes/Correct/CastInExprsAsserts/CastInExprsAsserts.p b/Tst/RegressionTests/Feature4DataTypes/Correct/CastInExprsAsserts/CastInExprsAsserts.p index 18a5e19683..6d79ce41f5 100644 --- a/Tst/RegressionTests/Feature4DataTypes/Correct/CastInExprsAsserts/CastInExprsAsserts.p +++ b/Tst/RegressionTests/Feature4DataTypes/Correct/CastInExprsAsserts/CastInExprsAsserts.p @@ -2,15 +2,15 @@ //XYZs static errors //Basic types: int, bool, event -event E assert 1: int; +event E : int; event EI1: int; event EI2: int; event EI3: int; event EI4: int; event EI5: int; event EI6: int; -event E1 assert 1; -event E2 assert 1; +event E1; +event E2; event ET1: (a: int, b: bool); event ET2: (a: int, b: bool); event ESEQ1: seq[int]; diff --git a/Tst/RegressionTests/Feature4DataTypes/Correct/EnumType/enumType.p b/Tst/RegressionTests/Feature4DataTypes/Correct/EnumType/enumType.p index 4525aacc2f..7752337890 100644 --- a/Tst/RegressionTests/Feature4DataTypes/Correct/EnumType/enumType.p +++ b/Tst/RegressionTests/Feature4DataTypes/Correct/EnumType/enumType.p @@ -3,10 +3,10 @@ //TODO(not included): anonymous tuples; //TODO(add later): XYZs for variables declared but not used -event E assert 1; -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; +event E; +event E1; +event E2; +event E3; enum Foo { foo0, foo1, foo2, foo3, foo4 } enum Bar { bar0, bar1, bar2, bar3 } diff --git a/Tst/RegressionTests/Feature4DataTypes/Correct/anyTypeNullValue/anyTypeNullValue.p b/Tst/RegressionTests/Feature4DataTypes/Correct/anyTypeNullValue/anyTypeNullValue.p index f05a883069..1f63eba79b 100644 --- a/Tst/RegressionTests/Feature4DataTypes/Correct/anyTypeNullValue/anyTypeNullValue.p +++ b/Tst/RegressionTests/Feature4DataTypes/Correct/anyTypeNullValue/anyTypeNullValue.p @@ -2,10 +2,10 @@ //XYZs that comparison of "any" type var with "null" is allowed //This XYZ found a bug in Zinger -event E assert 1; -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; +event E; +event E1; +event E2; +event E3; machine Main { var y : int; diff --git a/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes/nonAtomicDataTypes.p index f5ae31601b..51a70caa7c 100644 --- a/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes/nonAtomicDataTypes.p @@ -1,10 +1,10 @@ //XYZs complex data types in assign/remove/insert errors): sequences, tuples, maps //Basic types: int, bool, event -event E assert 1; -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; +event E; +event E1; +event E2; +event E3; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); diff --git a/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes12/StackOverflowException.p b/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes12/StackOverflowException.p index e0f0758d82..6ed7783288 100644 --- a/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes12/StackOverflowException.p +++ b/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes12/StackOverflowException.p @@ -1,5 +1,5 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps -event E assert 1; +event E; machine Main { var t, t1, tmp4: (a: seq [int], b: map[int, seq[int]]); var ts: (a: int, b: int); diff --git a/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes13/SeqAssgnToAny.p b/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes13/SeqAssgnToAny.p index 5556644103..113aefd33d 100644 --- a/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes13/SeqAssgnToAny.p +++ b/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes13/SeqAssgnToAny.p @@ -1,5 +1,5 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps -event E assert 1; +event E; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); var t1 : (a: seq [int], b: map[int, seq[int]]); diff --git a/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes16/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes16/nonAtomicDataTypes.p index cb2f70d929..a62964ef85 100644 --- a/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes16/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypes16/nonAtomicDataTypes.p @@ -1,10 +1,10 @@ //XYZs "Value must have a concrete type" error //Basic types: int, bool, event //This XYZ found a bug in Zinger -event E assert 1; -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; +event E; +event E1; +event E2; +event E3; machine Main { var y : int; diff --git a/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypesAllAsserts/nonAtomicDataTypesAllAsserts.p b/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypesAllAsserts/nonAtomicDataTypesAllAsserts.p index 29e651fce6..a95c580dad 100644 --- a/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypesAllAsserts/nonAtomicDataTypesAllAsserts.p +++ b/Tst/RegressionTests/Feature4DataTypes/Correct/nonAtomicDataTypesAllAsserts/nonAtomicDataTypesAllAsserts.p @@ -2,10 +2,10 @@ //XYZs static errors //Basic types: int, bool, event -event E assert 1; -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; +event E; +event E1; +event E2; +event E3; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); diff --git a/Tst/RegressionTests/Feature4DataTypes/Correct/string0/string0.p b/Tst/RegressionTests/Feature4DataTypes/Correct/string0/string0.p index b4ef1a4b1e..3e5539c77c 100644 --- a/Tst/RegressionTests/Feature4DataTypes/Correct/string0/string0.p +++ b/Tst/RegressionTests/Feature4DataTypes/Correct/string0/string0.p @@ -1,5 +1,5 @@ -event E assert 1; +event E; machine Main { var vAny: any; diff --git a/Tst/RegressionTests/Feature4DataTypes/Correct/stringcomp/stringcomp.p b/Tst/RegressionTests/Feature4DataTypes/Correct/stringcomp/stringcomp.p index 6b0c8c1d8a..6f4755df19 100644 --- a/Tst/RegressionTests/Feature4DataTypes/Correct/stringcomp/stringcomp.p +++ b/Tst/RegressionTests/Feature4DataTypes/Correct/stringcomp/stringcomp.p @@ -1,5 +1,5 @@ -event E assert 1; +event E; machine Main { var vAny: any; diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs1/CastInExprsDynError.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs1/CastInExprsDynError.p index 78795ca2a0..77f34a5439 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs1/CastInExprsDynError.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs1/CastInExprsDynError.p @@ -2,15 +2,15 @@ //XYZs dynamic error //Basic types: int, bool, event -event E assert 1: int; +event E : int; event EI1: int; event EI2: int; event EI3: int; event EI4: int; event EI5: int; event EI6: int; -event E1 assert 1; -event E2 assert 1; +event E1; +event E2; event ET1: (a: int, b: bool); event ET2: (a: int, b: bool); event ESEQ1: seq[int]; diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs2/CastInExprsDynError.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs2/CastInExprsDynError.p index 1091f1c446..0c3c985b0b 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs2/CastInExprsDynError.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs2/CastInExprsDynError.p @@ -2,15 +2,15 @@ //XYZs dynamic error //Basic types: int, bool, event -event E assert 1: int; +event E : int; event EI1: int; event EI2: int; event EI3: int; event EI4: int; event EI5: int; event EI6: int; -event E1 assert 1; -event E2 assert 1; +event E1; +event E2; event ET1: (a: int, b: bool); event ET2: (a: int, b: bool); event ESEQ1: seq[int]; diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs3/CastInExprsDynError.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs3/CastInExprsDynError.p index 248bff7495..aa0377dc2b 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs3/CastInExprsDynError.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs3/CastInExprsDynError.p @@ -2,15 +2,15 @@ //XYZs dynamic error //Basic types: int, bool, event -event E assert 1: int; +event E : int; event EI1: int; event EI2: int; event EI3: int; event EI4: int; event EI5: int; event EI6: int; -event E1 assert 1; -event E2 assert 1; +event E1; +event E2; event ET1: (a: int, b: bool); event ET2: (a: int, b: bool); event ESEQ1: seq[int]; diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs4/CastInExprsDynError.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs4/CastInExprsDynError.p index 54295cdb4d..09eaacf592 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs4/CastInExprsDynError.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs4/CastInExprsDynError.p @@ -2,15 +2,15 @@ //XYZs dynamic error //Basic types: int, bool, event -event E assert 1: int; +event E : int; event EI1: int; event EI2: int; event EI3: int; event EI4: int; event EI5: int; event EI6: int; -event E1 assert 1; -event E2 assert 1; +event E1; +event E2; event ET1: (a: int, b: bool); event ET2: (a: int, b: bool); event ESEQ1: seq[int]; diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs5/CastInExprsdynError.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs5/CastInExprsdynError.p index a4e111c3d9..3e0fee45c9 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs5/CastInExprsdynError.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs5/CastInExprsdynError.p @@ -2,15 +2,15 @@ //XYZs dynamic error //Basic types: int, bool, event -event E assert 1: int; +event E : int; event EI1: int; event EI2: int; event EI3: int; event EI4: int; event EI5: int; event EI6: int; -event E1 assert 1; -event E2 assert 1; +event E1; +event E2; event ET1: (a: int, b: bool); event ET2: (a: int, b: bool); event ESEQ1: seq[int]; diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs6/CastInExprsDynError.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs6/CastInExprsDynError.p index 2df5ee63b5..4e70ca8769 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs6/CastInExprsDynError.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/CastInExprs6/CastInExprsDynError.p @@ -2,15 +2,15 @@ //XYZs dynamic error //Basic types: int, bool, event -event E assert 1: int; +event E : int; event EI1: int; event EI2: int; event EI3: int; event EI4: int; event EI5: int; event EI6: int; -event E1 assert 1; -event E2 assert 1; +event E1; +event E2; event ET1: (a: int, b: bool); event ET2: (a: int, b: bool); event ESEQ1: seq[int]; diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/EnumType1/enumType.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/EnumType1/enumType.p index 073412cc6d..fe6084aafd 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/EnumType1/enumType.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/EnumType1/enumType.p @@ -1,10 +1,10 @@ //XYZs complex data types involving enum Types in assign/remove/insert errors): sequences, tuples, maps //XYZs casting error on line 131 -event E assert 1; -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; +event E; +event E1; +event E2; +event E3; enum Foo { foo0, foo1, foo2, foo3, foo4 } enum Bar { bar0, bar1, bar2, bar3 } diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType/anyType.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType/anyType.p index 466ff0542c..2326204819 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType/anyType.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType/anyType.p @@ -1,6 +1,6 @@ //XYZs "any" type in EQ and NE //TODO: Create separate XYZs for all failing asserts. -event E assert 1; +event E; machine Main { var vAny: any; diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType1/anyType1.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType1/anyType1.p index 9c6b93993f..4e78ba8d5c 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType1/anyType1.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType1/anyType1.p @@ -1,6 +1,6 @@ //XYZs "any" type in EQ and NE //TODO: Create separate XYZs for all failing asserts. -event E assert 1; +event E; machine Main { var vAny: any; diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType2/anyType2.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType2/anyType2.p index 09955217a2..859bff85d0 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType2/anyType2.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType2/anyType2.p @@ -1,6 +1,6 @@ //XYZs "any" type in EQ and NE //TODO: Create separate XYZs for all failing asserts. -event E assert 1; +event E; machine Main { var vAny: any; diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType3/anyType3.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType3/anyType3.p index 3d1d4126be..95b4e47c61 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType3/anyType3.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/anyType3/anyType3.p @@ -1,6 +1,6 @@ //XYZs "any" type in EQ and NE //TODO: Create separate XYZs for all failing asserts. -event E assert 1; +event E; machine Main { var vAny: any; diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes/nonAtomicDataTypes.p index 1731770077..a42de73973 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes/nonAtomicDataTypes.p @@ -1,7 +1,7 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps -event E assert 1; -event E1 assert 1; -event E2 assert 1; +event E; +event E1; +event E2; machine Main { var y, tmp, tmp1, i: int; var tmp2: (a: seq [any], b: map[int, seq[any]]); diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes1/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes1/nonAtomicDataTypes.p index 005b7807b1..2e38ba54ab 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes1/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes1/nonAtomicDataTypes.p @@ -1,6 +1,6 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps //XYZs "index-out-of-bounds" error detection in Zinger and runtime -event E assert 1; +event E; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); var ts: (a: int, b: int); diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes10/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes10/nonAtomicDataTypes.p index f21e9eb9e5..b0f01179cb 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes10/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes10/nonAtomicDataTypes.p @@ -1,6 +1,6 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps //XYZs "insert" for sequences errors -event E assert 1; +event E; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); var ts: (a: int, b: int); diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes11/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes11/nonAtomicDataTypes.p index b765cb7895..2c3a636a25 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes11/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes11/nonAtomicDataTypes.p @@ -1,6 +1,6 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps //XYZs "insert" for sequences errors -event E assert 1; +event E; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); var ts: (a: int, b: int); diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes14/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes14/nonAtomicDataTypes.p index 09e1e47e18..d2bcf12958 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes14/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes14/nonAtomicDataTypes.p @@ -1,6 +1,6 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps //XYZs "insert" for sequences errors -event E assert 1; +event E; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); var ts: (a: int, b: int); diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes2/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes2/nonAtomicDataTypes.p index 66ed861a04..f9d2469723 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes2/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes2/nonAtomicDataTypes.p @@ -1,6 +1,6 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps //XYZs "index-out-of-bounds" error detection in Zinger and runtime -event E assert 1; +event E; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); var ts: (a: int, b: int); diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes3/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes3/nonAtomicDataTypes.p index 0560b809b9..197ed8b39e 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes3/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes3/nonAtomicDataTypes.p @@ -1,6 +1,6 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps //XYZs "insert" for sequences errors -event E assert 1; +event E; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); var ts: (a: int, b: int); diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes4/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes4/nonAtomicDataTypes.p index b77f9f62b9..72125c9918 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes4/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes4/nonAtomicDataTypes.p @@ -1,6 +1,6 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps //XYZs "insert" for sequences errors -event E assert 1; +event E; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); var ts: (a: int, b: int); diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes5/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes5/nonAtomicDataTypes.p index dbeebdc64e..a889c4e87e 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes5/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes5/nonAtomicDataTypes.p @@ -1,6 +1,6 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps //XYZs "insert" for sequences errors -event E assert 1; +event E; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); var ts: (a: int, b: int); diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes6/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes6/nonAtomicDataTypes.p index a77ad1cf19..7952e36844 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes6/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes6/nonAtomicDataTypes.p @@ -1,6 +1,6 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps //XYZs "insert" for sequences errors -event E assert 1; +event E; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); var ts: (a: int, b: int); diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes7/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes7/nonAtomicDataTypes.p index 07852e3198..d0e6c694f3 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes7/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes7/nonAtomicDataTypes.p @@ -1,6 +1,6 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps //XYZs "insert" for sequences errors -event E assert 1; +event E; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); var ts: (a: int, b: int); diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes8/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes8/nonAtomicDataTypes.p index e87f06a3f8..7b49fda13d 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes8/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes8/nonAtomicDataTypes.p @@ -1,6 +1,6 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps //XYZs "insert" for sequences errors -event E assert 1; +event E; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); var ts: (a: int, b: int); diff --git a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes9/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes9/nonAtomicDataTypes.p index 8fd80c661c..dcb5587d62 100644 --- a/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes9/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/DynamicError/nonAtomicDataTypes9/nonAtomicDataTypes.p @@ -1,6 +1,6 @@ //XYZs complex data types in assign/remove/insert: sequences, tuples, maps //XYZs "insert" for sequences errors -event E assert 1; +event E; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); var ts: (a: int, b: int); diff --git a/Tst/RegressionTests/Feature4DataTypes/StaticError/CastInExprs/CastInExprs.p b/Tst/RegressionTests/Feature4DataTypes/StaticError/CastInExprs/CastInExprs.p index 21beadca80..a18f855ee4 100644 --- a/Tst/RegressionTests/Feature4DataTypes/StaticError/CastInExprs/CastInExprs.p +++ b/Tst/RegressionTests/Feature4DataTypes/StaticError/CastInExprs/CastInExprs.p @@ -2,7 +2,7 @@ //XYZs static errors; XYZ Correct\CastInExprsAsserts XYZs all asserts //Basic types: int, bool, event -event E assert 1: int; +event E : int; event EI1: int; event EI2: int; event EI3: int; diff --git a/Tst/RegressionTests/Feature4DataTypes/StaticError/EnumType/enumType.p b/Tst/RegressionTests/Feature4DataTypes/StaticError/EnumType/enumType.p index 89d45c47b3..c9f3a9657d 100644 --- a/Tst/RegressionTests/Feature4DataTypes/StaticError/EnumType/enumType.p +++ b/Tst/RegressionTests/Feature4DataTypes/StaticError/EnumType/enumType.p @@ -3,10 +3,10 @@ //TODO(not included): anonymous tuples; //TODO(add later): XYZs for variables declared but not used -event E assert 1; -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; +event E; +event E1; +event E2; +event E3; enum Foo { foo0, foo1, foo2, foo3, foo4 } enum Bar { bar0, bar1, bar2, bar3 } diff --git a/Tst/RegressionTests/Feature4DataTypes/StaticError/nonAtomicDataTypes/nonAtomicDataTypes.p b/Tst/RegressionTests/Feature4DataTypes/StaticError/nonAtomicDataTypes/nonAtomicDataTypes.p index 6c9cd856b7..dba4b2efb5 100644 --- a/Tst/RegressionTests/Feature4DataTypes/StaticError/nonAtomicDataTypes/nonAtomicDataTypes.p +++ b/Tst/RegressionTests/Feature4DataTypes/StaticError/nonAtomicDataTypes/nonAtomicDataTypes.p @@ -2,10 +2,10 @@ //XYZs static errors; XYZ Correct\nonAtomicDataTypes XYZs all asserts //Basic types: int, bool, event -event E assert 1; -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; +event E; +event E1; +event E2; +event E3; machine Main { var t : (a: seq [int], b: map[int, seq[int]]); diff --git a/Tst/RegressionTests/Feature4DataTypes/StaticError/string2/string2.p b/Tst/RegressionTests/Feature4DataTypes/StaticError/string2/string2.p index 80ef24c4e1..79b08a87a8 100644 --- a/Tst/RegressionTests/Feature4DataTypes/StaticError/string2/string2.p +++ b/Tst/RegressionTests/Feature4DataTypes/StaticError/string2/string2.p @@ -1,5 +1,5 @@ -event E assert 1; +event E; machine Main { var vAny: any; diff --git a/Tst/RegressionTests/Feature5ModuleSystem/Correct/Elevator/Elevator.p b/Tst/RegressionTests/Feature5ModuleSystem/Correct/Elevator/Elevator.p index 5731455bc6..26761a7f92 100644 --- a/Tst/RegressionTests/Feature5ModuleSystem/Correct/Elevator/Elevator.p +++ b/Tst/RegressionTests/Feature5ModuleSystem/Correct/Elevator/Elevator.p @@ -1,22 +1,22 @@ -event eOpenDoor assume 1; -event eCloseDoor assume 1; -event eResetDoor assert 1; -event eDoorOpened assert 1; -event eDoorClosed assert 1; -event eDoorStopped assert 1; -event eObjectDetected assert 1; -event eTimerFired assume 1; -event eOperationSuccess assert 1; -event eOperationFailure assert 1; -event eSendCommandToOpenDoor assume 1; -event eSendCommandToCloseDoor assume 1; -event eSendCommandToStopDoor assume 1; -event eSendCommandToResetDoor assume 1; -event eStartDoorCloseTimer assume 1; -event eStopDoorCloseTimer assume 1; -event eUnit assert 1; -event eStopTimerReturned assert 1; -event eObjectEncountered assert 1; +event eOpenDoor; +event eCloseDoor; +event eResetDoor; +event eDoorOpened; +event eDoorClosed; +event eDoorStopped; +event eObjectDetected; +event eTimerFired; +event eOperationSuccess; +event eOperationFailure; +event eSendCommandToOpenDoor; +event eSendCommandToCloseDoor; +event eSendCommandToStopDoor; +event eSendCommandToResetDoor; +event eStartDoorCloseTimer; +event eStopDoorCloseTimer; +event eUnit; +event eStopTimerReturned; +event eObjectEncountered; machine Elevator receives eOpenDoor, eCloseDoor, eDoorOpened, eTimerFired, eStopTimerReturned, eDoorClosed, eObjectDetected, eDoorStopped, diff --git a/Tst/RegressionTests/Feature5ModuleSystem/Correct/ElevatorMod/Elevator.p b/Tst/RegressionTests/Feature5ModuleSystem/Correct/ElevatorMod/Elevator.p index c667743b06..14b590b71c 100644 --- a/Tst/RegressionTests/Feature5ModuleSystem/Correct/ElevatorMod/Elevator.p +++ b/Tst/RegressionTests/Feature5ModuleSystem/Correct/ElevatorMod/Elevator.p @@ -1,22 +1,22 @@ -event eOpenDoor assume 1; -event eCloseDoor assume 1; -event eResetDoor assert 1; -event eDoorOpened assert 1; -event eDoorClosed assert 1; -event eDoorStopped assert 1; -event eObjectDetected assert 1; -event eTimerFired assume 1; -event eOperationSuccess assert 1; -event eOperationFailure assert 1; -event eSendCommandToOpenDoor assume 1; -event eSendCommandToCloseDoor assume 1; -event eSendCommandToStopDoor assume 1; -event eSendCommandToResetDoor assume 1; -event eStartDoorCloseTimer assume 1; -event eStopDoorCloseTimer assume 1; -event eUnit assert 1; -event eStopTimerReturned assert 1; -event eObjectEncountered assert 1; +event eOpenDoor; +event eCloseDoor; +event eResetDoor; +event eDoorOpened; +event eDoorClosed; +event eDoorStopped; +event eObjectDetected; +event eTimerFired; +event eOperationSuccess; +event eOperationFailure; +event eSendCommandToOpenDoor; +event eSendCommandToCloseDoor; +event eSendCommandToStopDoor; +event eSendCommandToResetDoor; +event eStartDoorCloseTimer; +event eStopDoorCloseTimer; +event eUnit; +event eStopTimerReturned; +event eObjectEncountered; machine Elevator diff --git a/Tst/RegressionTests/Feature5ModuleSystem/Correct/OSR/Headers.p b/Tst/RegressionTests/Feature5ModuleSystem/Correct/OSR/Headers.p index b1ad315ceb..c0a9648da8 100644 --- a/Tst/RegressionTests/Feature5ModuleSystem/Correct/OSR/Headers.p +++ b/Tst/RegressionTests/Feature5ModuleSystem/Correct/OSR/Headers.p @@ -1,23 +1,23 @@ //Events -event eD0Entry assume 1; -event eD0Exit assume 1; -event eTimerFired assert 1; -event eSwitchStatusChange assume 1; -event eTransferSuccess assume 1; -event eTransferFailure assume 1; -event eStopTimer assume 1; -event eUpdateBarGraphStateUsingControlTransfer assume 1; -event eSetLedStateToUnstableUsingControlTransfer assume 1; -event eStartDebounceTimer assume 1; -event eSetLedStateToStableUsingControlTransfer assume 1; -event eStoppingSuccess assert 1; -event eStoppingFailure assert 1; -event eOperationSuccess assert 1; -event eOperationFailure assert 1; -event eTimerStopped assert 1; -event eYes assert 1; -event eNo assert 1; -event eUnit assert 1; +event eD0Entry; +event eD0Exit; +event eTimerFired; +event eSwitchStatusChange; +event eTransferSuccess; +event eTransferFailure; +event eStopTimer; +event eUpdateBarGraphStateUsingControlTransfer; +event eSetLedStateToUnstableUsingControlTransfer; +event eStartDebounceTimer; +event eSetLedStateToStableUsingControlTransfer; +event eStoppingSuccess; +event eStoppingFailure; +event eOperationSuccess; +event eOperationFailure; +event eTimerStopped; +event eYes; +event eNo; +event eUnit; //interfaces diff --git a/Tst/RegressionTests/Feature5ModuleSystem/Correct/PingPongDingDongMod/Header.p b/Tst/RegressionTests/Feature5ModuleSystem/Correct/PingPongDingDongMod/Header.p index 917ccd1b8c..7caca3da8c 100644 --- a/Tst/RegressionTests/Feature5ModuleSystem/Correct/PingPongDingDongMod/Header.p +++ b/Tst/RegressionTests/Feature5ModuleSystem/Correct/PingPongDingDongMod/Header.p @@ -1,4 +1,4 @@ -event Ping assert 1 : machine; -event Pong assert 1; -event Ding assert 1; -event Dong assert 1; +event Ping : machine; +event Pong; +event Ding; +event Dong; diff --git a/Tst/RegressionTests/Integration/Correct/PingPong/PingPong.p b/Tst/RegressionTests/Integration/Correct/PingPong/PingPong.p index 1971bc08e7..572b8b3185 100644 --- a/Tst/RegressionTests/Integration/Correct/PingPong/PingPong.p +++ b/Tst/RegressionTests/Integration/Correct/PingPong/PingPong.p @@ -1,5 +1,5 @@ -event Ping assert 1 : machine; -event Pong assert 1; +event Ping : machine; +event Pong; event Success; machine Main { diff --git a/Tst/RegressionTests/Integration/Correct/PingPongDefer/PingPongDefer.p b/Tst/RegressionTests/Integration/Correct/PingPongDefer/PingPongDefer.p index 9c7ba35c88..ddd1626241 100644 --- a/Tst/RegressionTests/Integration/Correct/PingPongDefer/PingPongDefer.p +++ b/Tst/RegressionTests/Integration/Correct/PingPongDefer/PingPongDefer.p @@ -1,5 +1,5 @@ -event Ping assert 1 : machine; -event Pong assert 1; +event Ping : machine; +event Pong; event Success; machine Main { diff --git a/Tst/RegressionTests/Integration/Correct/PingPongDingDong/PingPongDingDong.p b/Tst/RegressionTests/Integration/Correct/PingPongDingDong/PingPongDingDong.p index 3b7a0a639a..1adbb73d65 100644 --- a/Tst/RegressionTests/Integration/Correct/PingPongDingDong/PingPongDingDong.p +++ b/Tst/RegressionTests/Integration/Correct/PingPongDingDong/PingPongDingDong.p @@ -1,8 +1,8 @@ -event Ping assert 1 : machine; -event Pong assert 1; -event Success assert 1; -event Ding assert 1; -event Dong assert 1; +event Ping : machine; +event Pong; +event Success; +event Ding; +event Dong; machine Main { diff --git a/Tst/RegressionTests/Integration/Correct/PingPongMonitor/PingPongMonitor.p b/Tst/RegressionTests/Integration/Correct/PingPongMonitor/PingPongMonitor.p index faf674ac8f..c974d336c7 100644 --- a/Tst/RegressionTests/Integration/Correct/PingPongMonitor/PingPongMonitor.p +++ b/Tst/RegressionTests/Integration/Correct/PingPongMonitor/PingPongMonitor.p @@ -1,5 +1,5 @@ -event Ping assert 1 : machine; -event Pong assert 1; +event Ping : machine; +event Pong; event Success; machine Main { diff --git a/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_18/UnhandledEventDeferred.p b/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_18/UnhandledEventDeferred.p index 8cbbeb3497..6ca5a8ce8b 100644 --- a/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_18/UnhandledEventDeferred.p +++ b/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_18/UnhandledEventDeferred.p @@ -1,8 +1,8 @@ // P semantics XYZ: one machine, deferral of an unhandled event // Compare this XYZ to SendInExitUnhandledEvent.p -event E2 assert 2; -event E1 assert 1; +event E2; +event E1; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_19/UnhandledEventIgnored.p b/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_19/UnhandledEventIgnored.p index dacb1db15c..37d44fe05c 100644 --- a/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_19/UnhandledEventIgnored.p +++ b/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_19/UnhandledEventIgnored.p @@ -1,8 +1,8 @@ // P semantics XYZ: one machine, "ignore" of an unhandled event // Compare this XYZ to SendInExitUnhandledEvent.p -event E2 assert 2; -event E1 assert 1; +event E2; +event E1; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_2/RaiseSendInEntry.p b/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_2/RaiseSendInEntry.p index 78766c70e3..aa58df3183 100644 --- a/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_2/RaiseSendInEntry.p +++ b/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_2/RaiseSendInEntry.p @@ -1,7 +1,7 @@ // P semantics XYZ: two machines, machine is halted with "raise halt" (unhandled) // Action2 is never executed after raising E1; XYZ passes -event E1 assert 1; -event E2 assert 1; +event E1; +event E2; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_31/RaisedHalt.p b/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_31/RaisedHalt.p index f8dcf00fb5..803b2d2f87 100644 --- a/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_31/RaisedHalt.p +++ b/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_31/RaisedHalt.p @@ -1,7 +1,7 @@ // P semantics XYZ: one machine, "halt" is raised and unhandled -event E2 assert 1; -event E1 assert 1; +event E2; +event E1; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_4/SendInExitNotExecuted.p b/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_4/SendInExitNotExecuted.p index 0891986f70..3d53ec8a41 100644 --- a/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_4/SendInExitNotExecuted.p +++ b/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_4/SendInExitNotExecuted.p @@ -1,6 +1,6 @@ // P semantics XYZ: one machine, "send" to itself in exit function // exit function is never executed, since control never leaves the Real1_Init state -event E2 assert 1; +event E2; machine Main { start state Real1_Init { diff --git a/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_7/SendInExitUnhandledHalt.p b/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_7/SendInExitUnhandledHalt.p index 0778693343..2448c35964 100644 --- a/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_7/SendInExitUnhandledHalt.p +++ b/Tst/RegressionTests/Integration/Correct/SEM_OneMachine_7/SendInExitUnhandledHalt.p @@ -5,7 +5,7 @@ // "halt" is not handled, since state Real1_Init is removed once goto is executed // Compare this XYZ to SendInExitUnhandledEvent.p -event E1 assert 1; +event E1; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_1/EventSentAfterSentHalt.p b/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_1/EventSentAfterSentHalt.p index 35b8134512..9d732f1512 100644 --- a/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_1/EventSentAfterSentHalt.p +++ b/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_1/EventSentAfterSentHalt.p @@ -1,7 +1,7 @@ // XYZs that event sent to a machine after it received the "halt" event is ignored by the halted machine // Case when "halt" is not explicitly handled, hence, PONG instance should be "halted" -event Ping assert 1 : machine; -event Pong assert 1; +event Ping : machine; +event Pong; event Success; event PingIgnored; //event PongHalted; diff --git a/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_14/NewMonitor_v.p b/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_14/NewMonitor_v.p index e9bf5fc4a2..3412a8b1da 100644 --- a/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_14/NewMonitor_v.p +++ b/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_14/NewMonitor_v.p @@ -1,6 +1,6 @@ // P semantics XYZ: two machines, announce announce instantiation parameter // This is validation XYZ for announceInvocation.p -event E2 assert 1: bool; +event E2 : bool; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_3/EventSentAfterSentHaltHandled.p b/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_3/EventSentAfterSentHaltHandled.p index e7bffb7af5..6bcc3baaf1 100644 --- a/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_3/EventSentAfterSentHaltHandled.p +++ b/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_3/EventSentAfterSentHaltHandled.p @@ -1,7 +1,7 @@ // XYZs that event sent to a machine after it received the "halt" event is ignored by the halted machine // Case when "halt" is explicitly handled -event Ping assert 1 : machine; -event Pong assert 1; +event Ping : machine; +event Pong; event Success; event PingIgnored; diff --git a/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_5/RaisedHalt.p b/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_5/RaisedHalt.p index 2675ddf2ea..f4b615dbdc 100644 --- a/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_5/RaisedHalt.p +++ b/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_5/RaisedHalt.p @@ -1,8 +1,8 @@ // P semantics XYZ: two machines, machine is halted with "raise halt" (unhandled) // This XYZ is for the case when "halt" is not explicitly handled - hence, default semantics of "halt" is engaged. // The program finishes in the deadlock in Ping_WaitPong after 2nd Pong is sent -event Ping assert 1 : machine; -event Pong assert 1; +event Ping : machine; +event Pong; event Success; event PongIgnored; //event PongHalted; diff --git a/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_7/RaisedHalt_bugFound.p b/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_7/RaisedHalt_bugFound.p index e950210856..5ae11be503 100644 --- a/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_7/RaisedHalt_bugFound.p +++ b/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_7/RaisedHalt_bugFound.p @@ -1,7 +1,7 @@ // P semantics XYZ: two machines, machine is halted with "raise halt" (unhandled) -event Ping assert 1 : machine; -event Pong assert 1; +event Ping : machine; +event Pong; event Success; event PongIgnored; diff --git a/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_8/HaltTrigger4Do.p b/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_8/HaltTrigger4Do.p index 91557d40e8..54d6c1aa01 100644 --- a/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_8/HaltTrigger4Do.p +++ b/Tst/RegressionTests/Integration/Correct/SEM_TwoMachines_8/HaltTrigger4Do.p @@ -1,7 +1,7 @@ // P semantics XYZ: two machines, "halt" as a transition trigger for "do" // Case when "halt" is explicitly handled -event Ping assert 1 : machine; -event Pong assert 1; +event Ping : machine; +event Pong; event Success; event PingIgnored; diff --git a/Tst/RegressionTests/Integration/Correct/TokenRing/TokenRing.p b/Tst/RegressionTests/Integration/Correct/TokenRing/TokenRing.p index 8a322770c5..a78aedd6d4 100644 --- a/Tst/RegressionTests/Integration/Correct/TokenRing/TokenRing.p +++ b/Tst/RegressionTests/Integration/Correct/TokenRing/TokenRing.p @@ -1,12 +1,12 @@ -event Empty assert 1 ; -event Sending assert 1 : machine ; -event Done assert 1 : machine ; -event Unit assert 0 ; -event Next assert 1 : machine ; +event Empty; +event Sending : machine ; +event Done : machine ; +event Unit; +event Next : machine ; event Send : machine ; event Ready ; -machine Node assume 100 { +machine Node { var IsSending : bool; var NextMachine : machine; diff --git a/Tst/RegressionTests/Integration/Correct/openwsn1/openwsn1.p b/Tst/RegressionTests/Integration/Correct/openwsn1/openwsn1.p index 4f703afbbf..28d52cd7f8 100644 --- a/Tst/RegressionTests/Integration/Correct/openwsn1/openwsn1.p +++ b/Tst/RegressionTests/Integration/Correct/openwsn1/openwsn1.p @@ -28,7 +28,7 @@ how do you machine the non det back off //this event is enqueued by the Timer machine. In real systems this will be replaced by the timer interrupt routine -event newSlot assert 1 : (bool, (machine,machine)); +event newSlot : (bool, (machine,machine)); event endSlot; event Local; event TxDone; @@ -36,9 +36,9 @@ event TxDone; event Tx; event Rx; event Sleep; -event Data assert 4 : (machine,int); -event Ack assert 1 : (machine,int); -event Initialize assert 1 : (machine,seq[machine]); +event Data : (machine,int); +event Ack : (machine,int); +event Initialize : (machine,seq[machine]); machine Main { var N1:machine;var N2:machine;var N3:machine;var N4:machine; diff --git a/Tst/RegressionTests/Integration/DynamicError/Actions_1/Actions_1.p b/Tst/RegressionTests/Integration/DynamicError/Actions_1/Actions_1.p index c100f7393f..eeae7a5981 100644 --- a/Tst/RegressionTests/Integration/DynamicError/Actions_1/Actions_1.p +++ b/Tst/RegressionTests/Integration/DynamicError/Actions_1/Actions_1.p @@ -1,9 +1,9 @@ // This sample XYZs basic semantics of actions and goto transitions -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event E4 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event E4; +event unit; machine Main { var ghost_machine: machine; diff --git a/Tst/RegressionTests/Integration/DynamicError/Actions_3/Actions_3.p b/Tst/RegressionTests/Integration/DynamicError/Actions_3/Actions_3.p index 53523d90e4..96af944b91 100644 --- a/Tst/RegressionTests/Integration/DynamicError/Actions_3/Actions_3.p +++ b/Tst/RegressionTests/Integration/DynamicError/Actions_3/Actions_3.p @@ -1,11 +1,11 @@ -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event E4 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event E4; +event unit; machine Main { var ghost_machine: machine; diff --git a/Tst/RegressionTests/Integration/DynamicError/Actions_5/Actions_5.p b/Tst/RegressionTests/Integration/DynamicError/Actions_5/Actions_5.p index 182912ad1e..5c1edfdf35 100644 --- a/Tst/RegressionTests/Integration/DynamicError/Actions_5/Actions_5.p +++ b/Tst/RegressionTests/Integration/DynamicError/Actions_5/Actions_5.p @@ -2,11 +2,11 @@ // (as for push statements) and inheritance of actions // compare to Actions_2.p (push statement case) -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event E4 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event E4; +event unit; machine Main { var ghost_machine: machine; diff --git a/Tst/RegressionTests/Integration/DynamicError/Actions_6/Actions_6.p b/Tst/RegressionTests/Integration/DynamicError/Actions_6/Actions_6.p index 682cfc4e44..b9b7bac62a 100644 --- a/Tst/RegressionTests/Integration/DynamicError/Actions_6/Actions_6.p +++ b/Tst/RegressionTests/Integration/DynamicError/Actions_6/Actions_6.p @@ -1,10 +1,10 @@ // This sample XYZs that payload works correctly with a push transition -event E1 assert 1; -event E2 assert 1 : int ; -event E3 assert 1; -event E4 assert 1; -event unit assert 1; +event E1; +event E2 : int ; +event E3; +event E4; +event unit; machine Main { var ghost_machine: machine; diff --git a/Tst/RegressionTests/Integration/DynamicError/Multi_Paxos_3/Multi_Paxos_3.p b/Tst/RegressionTests/Integration/DynamicError/Multi_Paxos_3/Multi_Paxos_3.p index bb1d8b17b1..ce0cfad167 100644 --- a/Tst/RegressionTests/Integration/DynamicError/Multi_Paxos_3/Multi_Paxos_3.p +++ b/Tst/RegressionTests/Integration/DynamicError/Multi_Paxos_3/Multi_Paxos_3.p @@ -1,11 +1,11 @@ /* The basic paxos algorithm. */ -event prepare assume 3: (proposer: machine, slot : int, proposal : (round: int, servermachine : int)) ; -event accept assume 3: (proposer: machine, slot: int, proposal : (round: int, servermachine : int), value : int); -event agree assume 6: (slot:int, proposal : (round: int, servermachine : int), value : int) ; -event reject assume 6: (slot: int, proposal : (round: int, servermachine : int)); -event accepted assume 6: (slot:int, proposal : (round: int, servermachine : int), value : int); +event prepare : (proposer: machine, slot : int, proposal : (round: int, servermachine : int)) ; +event accept : (proposer: machine, slot: int, proposal : (round: int, servermachine : int), value : int); +event agree : (slot:int, proposal : (round: int, servermachine : int), value : int) ; +event reject : (slot: int, proposal : (round: int, servermachine : int)); +event accepted : (slot:int, proposal : (round: int, servermachine : int), value : int); event local; event success; event allNodes: (nodes: seq[machine]); @@ -421,7 +421,7 @@ spec ValmachineityCheck observes announce_client_sent, announce_proposer_sent, a The leader election protocol for multi-paxos, the protocol is based on broadcast based approach. */ -event Ping assume 4: (rank:int, server : machine); +event Ping : (rank:int, server : machine); event newLeader : (rank:int, server : machine); event timeout : (mymachine : machine); event startTimer; diff --git a/Tst/RegressionTests/Integration/DynamicError/Multi_Paxos_4/Multi_Paxos_4.p b/Tst/RegressionTests/Integration/DynamicError/Multi_Paxos_4/Multi_Paxos_4.p index 3960f96811..dbbe9959db 100644 --- a/Tst/RegressionTests/Integration/DynamicError/Multi_Paxos_4/Multi_Paxos_4.p +++ b/Tst/RegressionTests/Integration/DynamicError/Multi_Paxos_4/Multi_Paxos_4.p @@ -3,11 +3,11 @@ /* The basic paxos algorithm. */ -event prepare assume 3: (proposer: machine, slot : int, proposal : (round: int, servermachine : int)) ; -event accept assume 3: (proposer: machine, slot: int, proposal : (round: int, servermachine : int), value : int); -event agree assume 6: (slot:int, proposal : (round: int, servermachine : int), value : int) ; -event reject assume 6: (slot: int, proposal : (round: int, servermachine : int)); -event accepted assume 6: (slot:int, proposal : (round: int, servermachine : int), value : int); +event prepare : (proposer: machine, slot : int, proposal : (round: int, servermachine : int)) ; +event accept : (proposer: machine, slot: int, proposal : (round: int, servermachine : int), value : int); +event agree : (slot:int, proposal : (round: int, servermachine : int), value : int) ; +event reject : (slot: int, proposal : (round: int, servermachine : int)); +event accepted : (slot:int, proposal : (round: int, servermachine : int), value : int); event local; event success; event allNodes: (nodes: seq[machine]); @@ -425,7 +425,7 @@ spec ValmachineityCheck observes announce_client_sent, announce_proposer_sent, a The leader election protocol for multi-paxos, the protocol is based on broadcast based approach. */ -event Ping assume 4 : (rank:int, server : machine) ; +event Ping : (rank:int, server : machine) ; event newLeader : (rank:int, server : machine); event timeout : (mymachine : machine); event startTimer; diff --git a/Tst/RegressionTests/Integration/DynamicError/PingPongWithCall/PingPongWithCall.p b/Tst/RegressionTests/Integration/DynamicError/PingPongWithCall/PingPongWithCall.p index 1d41b1933b..37dc848c4c 100644 --- a/Tst/RegressionTests/Integration/DynamicError/PingPongWithCall/PingPongWithCall.p +++ b/Tst/RegressionTests/Integration/DynamicError/PingPongWithCall/PingPongWithCall.p @@ -2,9 +2,9 @@ // the number of reachable states is unbounded" // unlimited number of "push" transitions with no "pop" // so zing is not running on this XYZ -event Ping assert 1 : machine; -event Pong assert 1; -event Success assert 1; +event Ping : machine; +event Pong; +event Success; machine Main { var pongId: machine; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_1/SendRaiseInEntry.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_1/SendRaiseInEntry.p index 30168046d5..6a7f753c54 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_1/SendRaiseInEntry.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_1/SendRaiseInEntry.p @@ -1,6 +1,6 @@ // P semantics XYZ: one machine, "send" to itself and "raise" in entry actions -event E1 assert 1; -event E2 assert 1; +event E1; +event E2; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_10/Push.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_10/Push.p index f1bd499108..8ff9985405 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_10/Push.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_10/Push.p @@ -2,8 +2,8 @@ // This XYZ checks that upon executing "goto" transition, exit function is executed, // but upon executing "push" transition, exit function is not executed -event E2 assert 1; -event E1 assert 1; +event E2; +event E1; machine Main { start state Real1_Init { diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_11/PushImplicitPopWithSend.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_11/PushImplicitPopWithSend.p index ae14d962f1..7f2e2397a6 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_11/PushImplicitPopWithSend.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_11/PushImplicitPopWithSend.p @@ -3,9 +3,9 @@ // Also, if a state is re-entered (meaning that the state was already on the stack), // entry function is not executed -event E2 assert 1; -event E1 assert 1; -event E3 assert 1; +event E2; +event E1; +event E3; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_12/PushExplicitPop.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_12/PushExplicitPop.p index 282f834ec0..0ee00104e2 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_12/PushExplicitPop.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_12/PushExplicitPop.p @@ -3,9 +3,9 @@ // Compare error trace for this XYZ with the one for PushTransInheritance.p -event E2 assert 1; -event E1 assert 1; -event E3 assert 1; +event E2; +event E1; +event E3; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_13/PushTransInheritance.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_13/PushTransInheritance.p index 240af1bb9e..2d24fc8331 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_13/PushTransInheritance.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_13/PushTransInheritance.p @@ -2,9 +2,9 @@ // This XYZ checks that after "push" transition, action of the pushing state is inherited by the pushed state // Compare error trace for this XYZ with the one for PushExplicitPop.p -event E2 assert 1; -event E1 assert 1; -event E3 assert 1; +event E2; +event E1; +event E3; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_14/GotoTransInheritance.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_14/GotoTransInheritance.p index e9ee42e92d..60baaa252a 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_14/GotoTransInheritance.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_14/GotoTransInheritance.p @@ -2,9 +2,9 @@ // This XYZ checks that after "goto" transition, action of the src state is not inherited by the dest state // Compare error trace for this XYZ with the one for PushTransInheritance.p -event E2 assert 1; -event E1 assert 1; -event E3 assert 1; +event E2; +event E1; +event E3; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_15/ImplicitPopExit.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_15/ImplicitPopExit.p index 82cb1fe528..3abe8d0852 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_15/ImplicitPopExit.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_15/ImplicitPopExit.p @@ -2,9 +2,9 @@ // This XYZ checks that when the state is implicitly popped, exit function of that state is executed // Compare this XYZ to PushImplicitPop.p -event E2 assert 1; -event E1 assert 1; -event E3 assert 1; +event E2; +event E1; +event E3; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_16/ExplicitPopExit.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_16/ExplicitPopExit.p index 55e9a6d224..ba25dc8e8e 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_16/ExplicitPopExit.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_16/ExplicitPopExit.p @@ -2,9 +2,9 @@ // This XYZ checks that when the state is explicitly popped, exit function of that state is executed // Compare this XYZ to PushExplicitPop.p and ImplicitPopExit.p -event E2 assert 1; -event E1 assert 1; -event E3 assert 1; +event E2; +event E1; +event E3; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_25/NewInExit.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_25/NewInExit.p index 533ee21d07..e00505f3df 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_25/NewInExit.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_25/NewInExit.p @@ -1,9 +1,9 @@ // P semantics XYZ: one machine, "new" in exit function -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event E4 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event E4; +event unit; machine Main { var ghost_machine: machine; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_28/DeferIgnore2.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_28/DeferIgnore2.p index 0d6e3e9c8b..9656208f30 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_28/DeferIgnore2.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_28/DeferIgnore2.p @@ -1,10 +1,10 @@ -event E1 assert 1; -event E2 assert 1; -event E3 assert 1; -event unit assert 1; +event E1; +event E2; +event E3; +event unit; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_3/SendInEntry.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_3/SendInEntry.p index 0ce73e1336..67dc3de7eb 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_3/SendInEntry.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_3/SendInEntry.p @@ -1,5 +1,5 @@ // P semantics XYZ: one machine, "send" to itself in entry actions -event E2 assert 1; +event E2; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_32/RaisedHaltHandled.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_32/RaisedHaltHandled.p index 47d292b523..a178f08fb7 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_32/RaisedHaltHandled.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_32/RaisedHaltHandled.p @@ -1,6 +1,6 @@ // P semantics XYZ: one machine, "halt" is raised and handled -event E1 assert 1; +event E1; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_36/NullEventHandler1.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_36/NullEventHandler1.p index dd4db20eff..e366d97c98 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_36/NullEventHandler1.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_36/NullEventHandler1.p @@ -1,8 +1,8 @@ // P semantics XYZ, one machine: "null" handler semantics // XYZing that null handler is enabled in the simplest case -event E1 assert 1; -event unit assert 1; +event E1; +event unit; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_39/TopNullHandlerOverridesInherited.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_39/TopNullHandlerOverridesInherited.p index f27a632426..6fc37e7f9b 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_39/TopNullHandlerOverridesInherited.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_39/TopNullHandlerOverridesInherited.p @@ -1,8 +1,8 @@ // P semantics XYZ: one machine, XYZing top "null" event handler overriding //inherited (by push transition) handler -event E1 assert 2; -event unit assert 1; +event E1; +event unit; event local; machine Main { diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_41/NullHandlerInLoop.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_41/NullHandlerInLoop.p index 27875694e5..7b6d4a92db 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_41/NullHandlerInLoop.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_41/NullHandlerInLoop.p @@ -2,8 +2,8 @@ // is executed in a loop // -event E1 assert 2; -event unit assert 1; +event E1; +event unit; event local; machine Main { diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_42/NullTriggerPayload.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_42/NullTriggerPayload.p index 1f65af8ea5..505ca23af1 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_42/NullTriggerPayload.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_42/NullTriggerPayload.p @@ -1,8 +1,8 @@ // P semantics XYZ: one machine, XYZing for "null" event, both // payload is null -event E1 assert 2; -event unit assert 1; +event E1; +event unit; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_5/SendInExitUnhandledEvent.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_5/SendInExitUnhandledEvent.p index a89a4edba8..5710063d07 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_5/SendInExitUnhandledEvent.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_5/SendInExitUnhandledEvent.p @@ -4,8 +4,8 @@ // Result: semantic error detected by Zing // Compare this XYZ with SendInExitHandledEvent.p -event E2 assert 1; -event E1 assert 1; +event E2; +event E1; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_6/SendInExitHandledEvent.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_6/SendInExitHandledEvent.p index 4083eeb082..a5cbab494f 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_6/SendInExitHandledEvent.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_6/SendInExitHandledEvent.p @@ -4,8 +4,8 @@ // Result: assert on line 26 is raised by Zing // Compare this XYZ with SendInExitUnhandledEvent.p -event E2 assert 1; -event E1 assert 1; +event E2; +event E1; machine Main { var XYZ: bool; //init with "false" diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_8/GotoToItself.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_8/GotoToItself.p index de61bc215f..35fa8475d0 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_8/GotoToItself.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_8/GotoToItself.p @@ -4,8 +4,8 @@ // E2 is handled by Action2 after entering Real1_Init upon "goto" transition // Result: assert on line 30 is raised by Zing -event E2 assert 1; -event E1 assert 1; +event E2; +event E1; machine Main { start state Real1_Init { diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_9/PushItself.p b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_9/PushItself.p index 802c286a75..3ba5bab080 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_9/PushItself.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_OneMachine_9/PushItself.p @@ -4,8 +4,8 @@ // Result: semantics error reported by Zing: // " Attempting to enqueue event ____E1 more than max instance of 1" -event E2 assert 1; -event E1 assert 1; +event E2; +event E1; machine Main { start state Real1_Init { diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_10/NonConstantEventExpr1.p b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_10/NonConstantEventExpr1.p index 8e4ae5e374..81a9b5a3ae 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_10/NonConstantEventExpr1.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_10/NonConstantEventExpr1.p @@ -1,7 +1,7 @@ // P semantics XYZ: two machines, "send", "raise" with payload for non-constant event expressions -event E1 assert 1; -event E2 assert 1: bool; +event E1; +event E2 : bool; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_12/NonConstantEventExprMonitor1.p b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_12/NonConstantEventExprMonitor1.p index 1e1ed9b2a7..44eed44e2f 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_12/NonConstantEventExprMonitor1.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_12/NonConstantEventExprMonitor1.p @@ -1,6 +1,6 @@ // P semantics XYZ: two machines, announce invocation with non-constant event expression -event E1 assert 1; -event E2 assert 1: bool; +event E1; +event E2 : bool; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_15/MonitorInvocationEventExprPayload.p b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_15/MonitorInvocationEventExprPayload.p index 319f78a01a..5e4df6c316 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_15/MonitorInvocationEventExprPayload.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_15/MonitorInvocationEventExprPayload.p @@ -1,7 +1,7 @@ // P semantics XYZ: two machines, announce invocation with non-constant event expression with payload // This XYZ found a bug (?) in pc.exe -event E1 assert 1; -event E2 assert 1: bool; +event E1; +event E2 : bool; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_16/NonConstantEventExpr2.p b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_16/NonConstantEventExpr2.p index bdca998100..d7984b7424 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_16/NonConstantEventExpr2.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_16/NonConstantEventExpr2.p @@ -1,7 +1,7 @@ // P semantics XYZ: two machines, "send", "raise" with non-constant event expressions // "raise" with non-constant event expression has non-null payload -event E1 assert 1: int; -event E2 assert 1: bool; +event E1 : int; +event E2 : bool; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_17/EventExprSemantics.p b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_17/EventExprSemantics.p index c6cc1ae3f1..0bea0245e8 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_17/EventExprSemantics.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_17/EventExprSemantics.p @@ -5,14 +5,14 @@ // This XYZ enables "Unhandled event" exception // (for E0 in state Real1_S1) -event E0 assert 1: any; -event E1 assert 1: any; -event E2 assert 5: event; -event E3 assert 1: bool; -event E4 assert 1: any; -event E5 assert 1: bool; -event E6 assert 1: int; -event E7 assert 1: bool; +event E0 : any; +event E1 : any; +event E2 : event; +event E3 : bool; +event E4 : any; +event E5 : bool; +event E6 : int; +event E7 : bool; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_18/EventExprSemantics1.p b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_18/EventExprSemantics1.p index 576ca105c5..3cd965139a 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_18/EventExprSemantics1.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_18/EventExprSemantics1.p @@ -5,14 +5,14 @@ // This XYZ enables "Unhandled event" exception // (for E0 in state Real1_S1) -event E0 assert 1: any; -event E1 assert 1: any; -event E2 assert 5: event; -event E3 assert 1: bool; -event E4 assert 1: any; -event E5 assert 1: bool; -event E6 assert 1: int; -event E7 assert 1: bool; +event E0 : any; +event E1 : any; +event E2 : event; +event E3 : bool; +event E4 : any; +event E5 : bool; +event E6 : int; +event E7 : bool; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_19/EventExprSemantics2.p b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_19/EventExprSemantics2.p index 4e50e2ef51..4011edc5b3 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_19/EventExprSemantics2.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_19/EventExprSemantics2.p @@ -4,14 +4,14 @@ // and then retrieved in Real2 and sent back to Real1 -event E0 assert 1: any; -event E1 assert 1: any; -event E2 assert 5: event; -event E3 assert 1: bool; -event E4 assert 1: any; -event E5 assert 1: bool; -event E6 assert 1: int; -event E7 assert 1: bool; +event E0 : any; +event E1 : any; +event E2 : event; +event E3 : bool; +event E4 : any; +event E5 : bool; +event E6 : int; +event E7 : bool; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_2/EventSentAfterSentHalt_v.p b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_2/EventSentAfterSentHalt_v.p index e84947da8a..8baf3f550c 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_2/EventSentAfterSentHalt_v.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_2/EventSentAfterSentHalt_v.p @@ -1,6 +1,6 @@ // XYZs that event sent to a machine after it received the "halt" event is ignored by the halted machine -event Ping assert 1 : machine; -event Pong assert 1; +event Ping : machine; +event Pong; event Success; event PingIgnored; event PongHalted; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_4/EventSentAfterSentHaltHandled_v.p b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_4/EventSentAfterSentHaltHandled_v.p index 08f4c83126..2be9b78dfd 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_4/EventSentAfterSentHaltHandled_v.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_4/EventSentAfterSentHaltHandled_v.p @@ -1,7 +1,7 @@ // XYZs that event sent to a machine after it received the "halt" event is ignored by the halted machine // Case when "halt" is explicitly handled -event Ping assert 1 : machine; -event Pong assert 1; +event Ping : machine; +event Pong; event Success; event PingIgnored; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_6/RaisedHaltHandled.p b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_6/RaisedHaltHandled.p index 4c30326a03..703d85f475 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_6/RaisedHaltHandled.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_6/RaisedHaltHandled.p @@ -1,8 +1,8 @@ // P semantics XYZ: two machines, machine is halted with "raise halt" (handled) // This XYZ is for the case when "halt" is explicitly handled - hence, it is processed as any other event. // -event Ping assert 1 : machine; -event Pong assert 1; +event Ping : machine; +event Pong; event Success; event PongIgnored; //event PongHalted; diff --git a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_9/NonConstantEventExpr.p b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_9/NonConstantEventExpr.p index 34d083e9fa..2d3a436bf0 100644 --- a/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_9/NonConstantEventExpr.p +++ b/Tst/RegressionTests/Integration/DynamicError/SEM_TwoMachines_9/NonConstantEventExpr.p @@ -1,7 +1,7 @@ // P semantics XYZ: two machines, "send", "raise", announce invocation for non-constant event expressions //This XYZ found null ptr deref bug in Zing -event E1 assert 1; -event E2 assert 1: bool; +event E1; +event E2 : bool; machine Main { var XYZ: bool; diff --git a/Tst/RegressionTests/Integration/StaticError/IncludeError/Node.p b/Tst/RegressionTests/Integration/StaticError/IncludeError/Node.p deleted file mode 100644 index 4a07204b8f..0000000000 --- a/Tst/RegressionTests/Integration/StaticError/IncludeError/Node.p +++ /dev/null @@ -1,79 +0,0 @@ -event Empty assert 1 ; -event Sending assert 1 : machine ; -event Done assert 1 : machine ; -event Unit assert 0 ; -event Next assert 1 : machine ; -event Send : machine ; -event Ready ; - -machine Node assume 100 { - - var IsSending : bool; - var NextMachine : machine; - var MyRing : machine; - - start state Init_Main_Node { - entry (payload: machine) { MyRing = payload; } - on Next goto SetNext_Main_Node; - } - - state Wait_Main_Node { - on Empty goto SendEmpty_Main_Node; - on Send goto StartSending_Main_Node; - on Sending goto KeepSending_Main_Node; - on Done goto StopSending_Main_Node; - } - - state SetNext_Main_Node { - entry (payload: machine) { - NextMachine = payload; - send MyRing, Ready; - raise Unit; - } - - on Unit goto Wait_Main_Node; - } - - state SendEmpty_Main_Node { - entry { - send NextMachine, Empty; - raise Unit; - } - - on Unit goto Wait_Main_Node; - } - - state StartSending_Main_Node { - entry (payload: machine) { - IsSending = true; - send NextMachine, Sending, payload; - raise Unit; - } - - on Unit goto Wait_Main_Node; - } - - state KeepSending_Main_Node { - entry (payload: machine) { - if (payload == this) - send NextMachine, Done, this; - else - send NextMachine, Sending, payload; - raise unit; - } - - on Unit goto Wait_Main_Node; - } - - state StopSending_Main_Node { - entry (payload: machine) { - if (IsSending == true) - send NextMachine, Empty; - else - send NextMachine, Done, payload; - raise Unit; - } - - on Unit goto Wait_Main_Node; - } -} diff --git a/Tst/RegressionTests/Integration/StaticError/IncludeError/TokenRing_Main.p b/Tst/RegressionTests/Integration/StaticError/IncludeError/TokenRing_Main.p deleted file mode 100644 index 639d8ffa6e..0000000000 --- a/Tst/RegressionTests/Integration/StaticError/IncludeError/TokenRing_Main.p +++ /dev/null @@ -1,84 +0,0 @@ -machine Main { - - var N1 : machine; - var N2 : machine; - var N3 : machine; - var N4 : machine; - var ReadyCount : int; - var Rand1 : bool; - var Rand2 : bool; - var RandSrc : machine; - var RandDst : machine; - - start state Boot_Main_Ring4 { - entry { - N1 = new Node(this); - N2 = new Node(this); - N3 = new Node(this); - N4 = new Node(this); - send N1, this, N2; - send N2, Next, N3; - send N3, Next, N4; - send N4, Next, N1; - ReadyCount = -1; - raise Unit; - } - - defer Ready; - on Unit goto Stabilize_Main_Ring4; - } - - state Stabilize_Main_Ring4 { - entry { - ReadyCount = ReadyCount + 1; - if (ReadyCount == 4) - raise Unit; - } - - on Ready goto Stabilize_Main_Ring4; - on Unit goto RandomComm_Main_Ring4; - } - - state RandomComm_Main_Ring4 { - entry { - if ($) - Rand1 = true; - else - Rand1 = false; - if ($) - Rand2 = true; - else - Rand2 = false; - - if (!Rand1 && !Rand2) - RandSrc = N1; - if (!Rand1 && Rand2) - RandSrc = N2; - if ((Rand1 && !Rand2) == 1) - RandSrc = N3; - else - RandSrc = N4; - if ($) - Rand1 = true; - else - Rand1 = false; - if ($) - Rand2 = true; - else - Rand2 = false; - if (!Rand1 && !Rand2) - RandDst = N1; - if (!Rand1 && Rand2) - RandDst = N2; - if (Rand1 && !Rand2) - RandDst = N3; - else - RandDst = N4; - - send RandSrc, Send, RandDst; - raise unit; - } - - on Unit goto RandomComm_Main_Ring4; - } -} diff --git a/Tst/RegressionTests/Integration/StaticError/TokenRing_Typos/TokenRing_Typos.p b/Tst/RegressionTests/Integration/StaticError/TokenRing_Typos/TokenRing_Typos.p index f18c63f7ee..f6e48abf4c 100644 --- a/Tst/RegressionTests/Integration/StaticError/TokenRing_Typos/TokenRing_Typos.p +++ b/Tst/RegressionTests/Integration/StaticError/TokenRing_Typos/TokenRing_Typos.p @@ -1,12 +1,12 @@ -event Empty assert 1 ; -event Sending assert 1 : machine ; -event Done assert 1 : machine ; -event Unit assert 0 ; -event Next assert 1 : machine ; +event Empty; +event Sending : machine ; +event Done : machine ; +event Unit; +event Next : machine ; event Send : machine ; event Ready ; -machine Node assume 100 { +machine Node { var IsSending : bool; var NextMachine : machine; diff --git a/Tst/RegressionTests/Liveness/Correct/Liveness_FAIRNONDET/Liveness_FAIRNONDET.p b/Tst/RegressionTests/Liveness/Correct/Liveness_FAIRNONDET/Liveness_FAIRNONDET.p index 714f0c8304..21c3f2ac0f 100644 --- a/Tst/RegressionTests/Liveness/Correct/Liveness_FAIRNONDET/Liveness_FAIRNONDET.p +++ b/Tst/RegressionTests/Liveness/Correct/Liveness_FAIRNONDET/Liveness_FAIRNONDET.p @@ -1,11 +1,11 @@ // Liveness XYZ: "check passed" // This is a simplest sample with FAIRNONDET in liveness -event UserEvent assert 1; -event Done assert 1; -event Loop assert 1; -event Waiting assert 1; -event Computing assert 1; +event UserEvent; +event Done; +event Loop; +event Waiting; +event Computing; machine Main { start state WaitForUser diff --git a/Tst/RegressionTests/Liveness/Correct/Liveness_FAIRNONDET2/Liveness_FAIRNONDET.p b/Tst/RegressionTests/Liveness/Correct/Liveness_FAIRNONDET2/Liveness_FAIRNONDET.p index 6ccbd86c9c..7663cb71fa 100644 --- a/Tst/RegressionTests/Liveness/Correct/Liveness_FAIRNONDET2/Liveness_FAIRNONDET.p +++ b/Tst/RegressionTests/Liveness/Correct/Liveness_FAIRNONDET2/Liveness_FAIRNONDET.p @@ -2,11 +2,11 @@ // This is a simplest sample with FAIRNONDET in liveness //checking symmetry in how FAIRNONDET works -event UserEvent assert 1; -event Done assert 1; -event Loop assert 1; -event Waiting assert 1; -event Computing assert 1; +event UserEvent; +event Done; +event Loop; +event Waiting; +event Computing; machine Main { start state WaitForUser diff --git a/Tst/RegressionTests/Liveness/DynamicError/InfiniteLoopInAtomicBlock/InfiniteLoopInAtomicBlock.p b/Tst/RegressionTests/Liveness/DynamicError/InfiniteLoopInAtomicBlock/InfiniteLoopInAtomicBlock.p index 5fa44bdbca..e1c623fdaf 100644 --- a/Tst/RegressionTests/Liveness/DynamicError/InfiniteLoopInAtomicBlock/InfiniteLoopInAtomicBlock.p +++ b/Tst/RegressionTests/Liveness/DynamicError/InfiniteLoopInAtomicBlock/InfiniteLoopInAtomicBlock.p @@ -1,8 +1,8 @@ // Zinger XYZ: infinite loop in atomic block // XYZs that event sent to a machine after it raised the "halt" event is ignored by the halted machine -event Ping assert 1 : machine; -event Pong assert 1; +event Ping : machine; +event Pong; event Success; event TimeToHalt; event PongIgnored; diff --git a/Tst/RegressionTests/Liveness/DynamicError/Liveness_NONDET/Liveness_NONDET.p b/Tst/RegressionTests/Liveness/DynamicError/Liveness_NONDET/Liveness_NONDET.p index cc5887e889..6be6373270 100644 --- a/Tst/RegressionTests/Liveness/DynamicError/Liveness_NONDET/Liveness_NONDET.p +++ b/Tst/RegressionTests/Liveness/DynamicError/Liveness_NONDET/Liveness_NONDET.p @@ -2,11 +2,11 @@ // This is a simplest sample with NONDET in liveness // - compare this XYZ with Correct\Liveness_FAIRNONDET -event UserEvent assert 1; -event Done assert 1; -event Loop assert 1; -event Waiting assert 1; -event Computing assert 1; +event UserEvent; +event Done; +event Loop; +event Waiting; +event Computing; machine Main { start state WaitForUser diff --git a/Tst/RegressionTests/Liveness/DynamicError/Liveness_NONDET2/Liveness_NONDET.p b/Tst/RegressionTests/Liveness/DynamicError/Liveness_NONDET2/Liveness_NONDET.p index 7f72a0d945..c604e2829e 100644 --- a/Tst/RegressionTests/Liveness/DynamicError/Liveness_NONDET2/Liveness_NONDET.p +++ b/Tst/RegressionTests/Liveness/DynamicError/Liveness_NONDET2/Liveness_NONDET.p @@ -2,11 +2,11 @@ // This is a simplest sample with NONDET in liveness // - compare this XYZ with Correct\Liveness_FAIRNONDET -event UserEvent assert 1; -event Done assert 1; -event Loop assert 1; -event Waiting assert 1; -event Computing assert 1; +event UserEvent; +event Done; +event Loop; +event Waiting; +event Computing; machine Main { start state WaitForUser diff --git a/Tutorial/Advanced/1_ChainReplicationVerification/.gitignore b/Tutorial/Advanced/1_ChainReplicationVerification/.gitignore new file mode 100644 index 0000000000..161a65f6d8 --- /dev/null +++ b/Tutorial/Advanced/1_ChainReplicationVerification/.gitignore @@ -0,0 +1,7 @@ +*.out +*.err +portfolio/ +Test.cs +pom.xml +*.csproj +PCheckerOutput/ diff --git a/Tutorial/Advanced/1_ChainReplicationVerification/ChainReplicationVerification.pproj b/Tutorial/Advanced/1_ChainReplicationVerification/ChainReplicationVerification.pproj new file mode 100644 index 0000000000..10a22b1b80 --- /dev/null +++ b/Tutorial/Advanced/1_ChainReplicationVerification/ChainReplicationVerification.pproj @@ -0,0 +1,8 @@ + +ChainReplicationVerification + + ./PSrc/ + +./PGenerated/ +PVerifier + diff --git a/Tutorial/Advanced/1_ChainReplicationVerification/PSrc/System.p b/Tutorial/Advanced/1_ChainReplicationVerification/PSrc/System.p new file mode 100644 index 0000000000..a6e36f1cf2 --- /dev/null +++ b/Tutorial/Advanced/1_ChainReplicationVerification/PSrc/System.p @@ -0,0 +1,128 @@ +type tWriteRequest = (source: machine, k: int, v: int); +type tReadRequest = (source: machine, k: int); +type tReadResponse = (source: machine, k: int, v: int, status: bool); + +event eWriteRequest : tWriteRequest; +event eWriteResponse : tWriteRequest; + +event ePropagateWrite: tWriteRequest; + +event eReadRequest : tReadRequest; +event eReadResponse : tReadResponse; + +machine Head { + var kv: map[int, int]; + + start state Receiving { + on eWriteRequest do (req: tWriteRequest) { + kv[req.k] = req.v; + send next_(this), ePropagateWrite, (source = req.source, k = req.k, v = req.v); + } + } +} + +machine Body { + var kv: map[int, int]; + + start state Forwarding { + on ePropagateWrite do (req: tWriteRequest) { + kv[req.k] = req.v; + send next_(this), ePropagateWrite, (source = req.source, k = req.k, v = req.v); + } + } +} + + +machine Tail { + var kv: map[int, int]; + + start state Replying { + on ePropagateWrite do (req: tWriteRequest) { + kv[req.k] = req.v; + send req.source, eWriteResponse, (source = req.source, k = req.k, v = req.v); + } + + on eReadRequest do (req: tReadRequest) { + if (req.k in kv) { + send req.source, eReadResponse, (source = req.source, k = req.k, v = kv[req.k], status = true); + } else { + send req.source, eReadResponse, (source = req.source, k = req.k, v = -1, status = false); + } + } + } +} + +machine Client { + start state Loop { + entry { + var k: int; + var v: int; + + k = RandomInt(); + + if ($) { + v = RandomInt(); + send head(), eWriteRequest, (source = this, k = k, v = v); + } else { + send tail(), eReadRequest, (source = this, k = k); + } + + goto Loop; + } + ignore eWriteResponse, eReadResponse; // not actually doing anything with these in the client + } +} + +fun RandomInt(): int; + +spec StrongConsistency observes eReadResponse, eWriteResponse { + var kv : map[int, int]; + start state WaitForEvents { + on eWriteResponse do (resp: tWriteRequest) { + kv[resp.k] = resp.v; + } + on eReadResponse do (resp: tReadResponse) { + if (resp.k in kv) { + assert resp.status == true; + assert resp.v == kv[resp.k]; + } else { + assert resp.status == false; + } + } + } +} + +// begin proof +pure head(): machine; +pure tail(): machine; +pure next_(m: machine): machine; + +// set all the fields to their default values +init-condition forall (p: Head) :: p.kv == default(map[int, int]); +init-condition forall (p: Body) :: p.kv == default(map[int, int]); +init-condition forall (p: Tail) :: p.kv == default(map[int, int]); +init-condition StrongConsistency.kv == default(map[int, int]); + +// assume the pure functions match the state at the start +init-condition forall (m: machine) :: m == head() == m is Head; +init-condition forall (m: machine) :: m == tail() == m is Tail; +init-condition forall (m: machine) :: next_(m) is Body || next_(m) is Tail; +// ensure that the pure functions continue to match the state as time goes on +invariant next_1: forall (m: machine) :: m == head() == m is Head; +invariant next_2: forall (m: machine) :: m == tail() == m is Tail; +invariant next_3: forall (m: machine) :: next_(m) is Body || next_(m) is Tail; + +// Aux invariants for P obligations +invariant only_head_receives_writes: forall (m: machine, e: event) :: (inflight e && e is eWriteRequest && e targets m) ==> (m is Head); +invariant only_tail_receives_reads: forall (m: machine, e: event) :: (inflight e && e is eReadRequest && e targets m) ==> (m is Tail); +invariant only_client_receives_read_response: forall (m: machine, e: event) :: (inflight e && e is eReadResponse && e targets m) ==> (m is Client); +invariant only_client_receives_write_response: forall (m: machine, e: event) :: (inflight e && e is eWriteResponse && e targets m) ==> (m is Client); +invariant only_body_or_tail_receive_props: forall (m: machine, e: event) :: (inflight e && e is ePropagateWrite && e targets m) ==> (m is Tail || m is Body); +invariant source_is_always_client_1: forall (e: eWriteRequest) :: inflight e ==> e.source is Client; +invariant source_is_always_client_2: forall (e: eReadRequest) :: inflight e ==> e.source is Client; +invariant source_is_always_client_3: forall (e: ePropagateWrite) :: inflight e ==> e.source is Client; +invariant source_is_always_client_4: forall (e: eReadResponse) :: inflight e ==> e.source is Client; +invariant source_is_always_client_5: forall (e: eWriteResponse) :: inflight e ==> e.source is Client; + +// Aux invariant for spec machine +invariant tail_sync: forall (t: Tail) :: t.kv == StrongConsistency.kv; diff --git a/Tutorial/Advanced/1_ChainReplicationVerification/portfolio-config.json b/Tutorial/Advanced/1_ChainReplicationVerification/portfolio-config.json new file mode 100644 index 0000000000..f911404168 --- /dev/null +++ b/Tutorial/Advanced/1_ChainReplicationVerification/portfolio-config.json @@ -0,0 +1,7 @@ +{ + "pproj": { + "help" : "Name of the .pproj file in the project directory", + "default" : "ChainReplicationVerification.pproj", + "metavar" : "F" + } +} diff --git a/Tutorial/Advanced/2_TwoPhaseCommitVerification/.gitignore b/Tutorial/Advanced/2_TwoPhaseCommitVerification/.gitignore new file mode 100644 index 0000000000..161a65f6d8 --- /dev/null +++ b/Tutorial/Advanced/2_TwoPhaseCommitVerification/.gitignore @@ -0,0 +1,7 @@ +*.out +*.err +portfolio/ +Test.cs +pom.xml +*.csproj +PCheckerOutput/ diff --git a/Tutorial/Advanced/2_TwoPhaseCommitVerification/KeyValue/PSrc/System.p b/Tutorial/Advanced/2_TwoPhaseCommitVerification/KeyValue/PSrc/System.p new file mode 100644 index 0000000000..1d83cc139e --- /dev/null +++ b/Tutorial/Advanced/2_TwoPhaseCommitVerification/KeyValue/PSrc/System.p @@ -0,0 +1,246 @@ +/********************************************************* +Clients send read and write requests to a coordinator. On write requests, the coordinator does a two phase commit with +all participants. On read requests, the coordinator just asks a random participant to respond to the client. + +All participants must agree on every commited write. The monitor keeps track of all write responses, which is the ground +truth for what is commited. Every participant must be maintaining a subset of this. +**********************************************************/ + +enum tVote {YES, NO} + +type tRound = int; +type tKey = int; +type tValue = int; + +type rvPair = (round: tRound, value: tValue); +type krvTriple = (key: tKey, round: tRound, value: tValue); + +type tVoteResp = (origin: machine, triple: krvTriple, vote: tVote, source: machine); +type tVoteReq = (origin: machine, triple: krvTriple); +type tWriteReq = (origin: machine, key: tKey, value: tValue); +type tReadReq = (origin: machine, key: tKey); +type tWriteResp = (triple: krvTriple, vote: tVote); +type tReadResp = (key: tKey, versions: set[rvPair], source: machine); + +event eVoteReq : tVoteReq; +event eVoteResp: tVoteResp; +event eAbort : tVoteReq; +event eCommit : tVoteReq; + +event eWriteReq : tWriteReq; +event eWriteResp: tWriteResp; +event eReadReq : tReadReq; +event eReadResp : tReadResp; + +fun RandomInt(): int; +fun RandomParticipant(s: set[machine]) + return (x: machine); + ensures x in s; + +machine Client { + start state Loop { + entry { + var k: int; + var v: int; + + k = RandomInt(); + v = RandomInt(); + + if ($) { + v = RandomInt(); + send coordinator(), eWriteReq, (origin = this, key = k, value = v); + } else { + send coordinator(), eReadReq, (origin = this, key = k); + } + + goto Loop; + } + ignore eWriteResp, eReadResp; + } +} + +machine Coordinator +{ + var yesVotes: map[krvTriple, set[machine]]; + var commited: set[krvTriple]; + var aborted : set[krvTriple]; + + start state Serving { + on eReadReq do (req: tReadReq) { + var p: machine; + p = RandomParticipant(participants()); + send p, eReadReq, req; + } + + on eWriteReq do (req: tWriteReq) { + var p: machine; + var r: tRound; + assume (forall (t: krvTriple) :: t in yesVotes ==> t.round != r); + + yesVotes[(key = req.key, round = r, value = req.value)] = default(set[machine]); + + foreach (p in participants()) + invariant forall new (e: event) :: forall (m: machine) :: e targets m ==> m in participants(); + invariant forall new (e: event) :: e is eVoteReq; + invariant forall new (e: eVoteReq) :: e.triple.round == r && e.triple.key == req.key && e.triple.value == req.value && e.origin == req.origin; + { + send p, eVoteReq, (origin = req.origin, triple = (key = req.key, round = r, value = req.value)); + } + } + + on eVoteResp do (resp: tVoteResp) { + var p: machine; + + if (resp.vote == YES) { + yesVotes[resp.triple] += (resp.source); + + if (yesVotes[resp.triple] == participants()) { + foreach (p in participants()) + invariant forall new (e: event) :: forall (m: machine) :: e targets m ==> m in participants(); + invariant forall new (e: event) :: e is eCommit; + invariant forall new (e: eCommit) :: e.triple == resp.triple && e.origin == resp.origin; + { + send p, eCommit, (origin = resp.origin, triple = resp.triple); + } + + commited += (resp.triple); + send resp.origin, eWriteResp, (triple = resp.triple, vote = resp.vote); + goto Serving; + } + + } else { + foreach (p in participants()) + invariant forall new (e: event) :: forall (m: machine) :: e targets m ==> m in participants(); + invariant forall new (e: event) :: e is eAbort; + invariant forall new (e: eAbort) :: e.triple == resp.triple && e.origin == resp.origin; + { + send p, eAbort, (origin = resp.origin, triple = resp.triple); + } + + aborted += (resp.triple); + send resp.origin, eWriteResp, (triple = resp.triple, vote = resp.vote); + goto Serving; + } + } + } +} + +machine Participant { + var commited: set[krvTriple]; + var aborted: set[krvTriple]; + var kv: map[tKey, set[rvPair]]; + + start state Acting { + on eVoteReq do (req: tVoteReq) { + send coordinator(), eVoteResp, (origin = req.origin, triple = req.triple, vote = preference(this, req.triple), source = this); + } + + on eCommit do (req: tVoteReq) { + commited += (req.triple); + if (!(req.triple.key in kv)) { + kv[req.triple.key] = default(set[rvPair]); + } + kv[req.triple.key] += ((round = req.triple.round, value = req.triple.value)); + } + + on eAbort do (req: tVoteReq) { + aborted += (req.triple); + } + + on eReadReq do (req: tReadReq) { + if (req.key in kv) { + send req.origin, eReadResp, (key = req.key, versions = kv[req.key], source = this); + } else { + send req.origin, eReadResp, (key = req.key, versions = default(set[rvPair]), source = this); + } + } + } +} + +spec Consistency observes eReadResp, eWriteResp { + var kv: map[tKey, set[rvPair]]; + start state WaitForEvents { + on eWriteResp do (resp: tWriteResp) { + if (resp.vote == YES) { + kv[resp.triple.key] += ((round = resp.triple.round, value = resp.triple.value)); + } + } + on eReadResp do (resp: tReadResp) { + // Everything we send back was actually written. + assert subset(resp.versions, kv[resp.key]); + // Everyone agrees on what we send back + assert (forall (t: krvTriple) :: (resp.key == t.key && (round = t.round, value = t.value) in resp.versions) ==> (forall (p: Participant) :: preference(p, t) == YES)); + } + } +} + +// using these to avoid initialization +pure participants(): set[machine]; +pure coordinator(): machine; +pure preference(m: machine, triple: krvTriple) : tVote; + +pure subset(small: set[rvPair], large: set[rvPair]) : bool = forall (rv: rvPair) :: rv in small ==> rv in large; + +// lets assume that there is at least one participant +axiom exists (x: machine) :: x in participants(); + +// assumptions about how the system is setup and the pure functions above +init-condition coordinator() is Coordinator; +init-condition forall (c1: machine, c2: machine) :: (c1 is Coordinator && c2 is Coordinator) ==> c1 == c2; +init-condition forall (m: machine) :: m in participants() == m is Participant; + +// making sure that our assumptions about pure functions are not pulled out from underneath us +invariant c_is_coordinator: coordinator() is Coordinator; +invariant one_coordinator: forall (c1: machine, c2: machine) :: (c1 is Coordinator && c2 is Coordinator) ==> c1 == c2; +invariant participant_set: forall (m: machine) :: m in participants() == m is Participant; + +// set all the fields to their default values +init-condition forall (c: Coordinator) :: c.yesVotes == default(map[krvTriple, set[machine]]); +init-condition forall (c: Coordinator) :: c.commited == default(set[krvTriple]); +init-condition forall (c: Coordinator) :: c.aborted == default(set[krvTriple]); +init-condition forall (p: Participant) :: p.commited == default(set[krvTriple]); +init-condition forall (p: Participant) :: p.aborted == default(set[krvTriple]); +init-condition forall (p: Participant) :: p.kv == default(map[tKey, set[rvPair]]); +init-condition Consistency.kv == default(map[tKey, set[rvPair]]); + +// make sure we never get a message that we're not expecting +invariant never_commit_to_coordinator: forall (e: event) :: e is eCommit && e targets coordinator() ==> !inflight e; +invariant never_abort_to_coordinator: forall (e: event) :: e is eAbort && e targets coordinator() ==> !inflight e; +invariant never_req_to_coordinator: forall (e: event) :: e is eVoteReq && e targets coordinator() ==> !inflight e; +invariant never_resp_to_participant: forall (e: event, p: Participant) :: e is eVoteResp && e targets p ==> !inflight e; +invariant never_writeReq_to_participant: forall (e: event, p: Participant) :: e is eWriteReq && e targets p ==> !inflight e; +invariant never_writeResp_to_participant: forall (e: event, p: Participant) :: e is eWriteResp && e targets p ==> !inflight e; +invariant never_readResp_to_participant: forall (e: event, p: Participant) :: e is eReadResp && e targets p ==> !inflight e; +invariant never_writeResp_to_coordinator: forall (e: event, c: Coordinator) :: e is eWriteResp && e targets c ==> !inflight e; +invariant never_readResp_to_coordinator: forall (e: event, c: Coordinator) :: e is eReadResp && e targets c ==> !inflight e; +invariant never_voteReq_to_client: forall (e: event, c: Client) :: e is eVoteReq && e targets c ==> !inflight e; +invariant never_voteResp_to_client: forall (e: event, c: Client) :: e is eVoteResp && e targets c ==> !inflight e; +invariant never_abort_to_client: forall (e: event, c: Client) :: e is eAbort && e targets c ==> !inflight e; +invariant never_commit_to_client: forall (e: event, c: Client) :: e is eCommit && e targets c ==> !inflight e; +invariant never_writeReq_to_client: forall (e: event, c: Client) :: e is eWriteReq && e targets c ==> !inflight e; +invariant never_readReq_to_client: forall (e: event, c: Client) :: e is eReadReq && e targets c ==> !inflight e; +invariant readReq_origin_is_client: forall (e: eReadReq) :: inflight e ==> e.origin is Client; +invariant writeReq_origin_is_client: forall (e: eWriteReq) :: inflight e ==> e.origin is Client; +invariant voteReq_origin_is_client: forall (e: eVoteReq) :: inflight e ==> e.origin is Client; +invariant voteResp_origin_is_client: forall (e: eVoteResp) :: inflight e ==> e.origin is Client; + +// aux invariants for consistency monitor first assertion +invariant p_subset_monitor_kv: forall (p: Participant, k: tKey) :: k in p.kv ==> subset(p.kv[k], Consistency.kv[k]); +invariant c_commited_means_in_monitor_kv: forall (c: Coordinator, t: krvTriple) :: t in c.commited ==> ((round = t.round, value = t.value) in Consistency.kv[t.key]); + +// aux invariants for consistency monitor second assertion +// essentially the 2pc property, but restated for kv elements +invariant in_kv_means_all_preferred: forall (p1: Participant, t: krvTriple) :: (t.key in p1.kv && (round = t.round, value = t.value) in p1.kv[t.key]) ==> (forall (p2: Participant) :: preference(p2, t) == YES); + +// supporting invariants for the 2pc property +invariant a1: forall (e: eVoteResp) :: sent e ==> e.source is Participant; +invariant a2: forall (e: eVoteResp) :: sent e ==> e.vote == preference(e.source, e.triple); +invariant a3a: forall (c: Coordinator, e: eCommit) :: sent e ==> e.triple in c.commited; +invariant a3b: forall (c: Coordinator, e: eAbort) :: sent e ==> e.triple in c.aborted; +invariant a4: forall (c: Coordinator, t: krvTriple, p1: Participant) :: t in p1.commited ==> t in c.commited; +invariant a5: forall (p: Participant, c: Coordinator, t: krvTriple) :: (t in c.yesVotes && p in c.yesVotes[t]) ==> preference(p, t) == YES; +invariant a6: forall (c: Coordinator, t: krvTriple) :: t in c.commited ==> (forall (p2: Participant) :: preference(p2, t) == YES); +invariant a7a: forall (c: Coordinator, e: eVoteReq) :: sent e ==> e.triple in c.yesVotes; +invariant a7b: forall (c: Coordinator, e: eVoteResp) :: sent e ==> e.triple in c.yesVotes; + + diff --git a/Tutorial/Advanced/2_TwoPhaseCommitVerification/KeyValue/TwoPhaseCommitVerification.pproj b/Tutorial/Advanced/2_TwoPhaseCommitVerification/KeyValue/TwoPhaseCommitVerification.pproj new file mode 100644 index 0000000000..f76aad3a17 --- /dev/null +++ b/Tutorial/Advanced/2_TwoPhaseCommitVerification/KeyValue/TwoPhaseCommitVerification.pproj @@ -0,0 +1,8 @@ + +TwoPhaseCommitVerification + + ./PSrc/ + +./PGenerated/ +PVerifier + diff --git a/Tutorial/Advanced/2_TwoPhaseCommitVerification/Rounds/PSrc/System.p b/Tutorial/Advanced/2_TwoPhaseCommitVerification/Rounds/PSrc/System.p new file mode 100644 index 0000000000..d321afe444 --- /dev/null +++ b/Tutorial/Advanced/2_TwoPhaseCommitVerification/Rounds/PSrc/System.p @@ -0,0 +1,136 @@ +enum Vote {YES, NO} +type Round = int; +type tVoteResp = (source: machine, round: Round, vote: Vote); +type tVoteReq = (round: Round); + +event eVoteReq: tVoteReq; +event eVoteResp: tVoteResp; +event eAbort: tVoteReq; +event eCommit: tVoteReq; + +machine Coordinator +{ + var yesVotes: map[Round, set[machine]]; + var commited: set[Round]; + var aborted: set[Round]; + + start state Init { + entry { + var p: machine; + var r: Round; + assume !(r in yesVotes); + + yesVotes[r] = default(set[machine]); + + foreach (p in participants()) + invariant forall new (e: event) :: forall (m: machine) :: e targets m ==> m in participants(); + invariant forall new (e: event) :: e is eVoteReq; + invariant forall new (e: eVoteReq) :: e.round == r; + { + send p, eVoteReq, (round = r,); + } + + goto WaitForResponses; + } + + defer eVoteResp; + } + + state WaitForResponses { + on eVoteResp do (resp: tVoteResp) { + var p: machine; + + if (resp.vote == YES) { + yesVotes[resp.round] += (resp.source); + + if (yesVotes[resp.round] == participants()) { + foreach (p in participants()) + invariant forall new (e: event) :: forall (m: machine) :: e targets m ==> m in participants(); + invariant forall new (e: event) :: e is eCommit; + invariant forall new (e: eCommit) :: e.round == resp.round; + { + send p, eCommit, (round = resp.round,); + } + + commited += (resp.round); + goto Init; + } + + } else { + foreach (p in participants()) + invariant forall new (e: event) :: forall (m: machine) :: e targets m ==> m in participants(); + invariant forall new (e: event) :: e is eAbort; + invariant forall new (e: eAbort) :: e.round == resp.round; + { + send p, eAbort, (round = resp.round,); + } + + aborted += (resp.round); + goto Init; + } + } + } +} + +machine Participant { + var commited: set[Round]; + var aborted: set[Round]; + + start state Undecided { + on eVoteReq do (req: tVoteReq) { + send coordinator(), eVoteResp, (source = this, round = req.round, vote = preference(this, req.round)); + } + + on eCommit do (req: tVoteReq) { + commited += (req.round); + } + + on eAbort do (req: tVoteReq) { + aborted += (req.round); + } + } +} + +// using these to avoid initialization +pure participants(): set[machine]; +pure coordinator(): machine; +pure preference(m: machine, r: Round) : Vote; + +// assumptions about how the system is setup and the pure functions above +init-condition forall (m: machine) :: m == coordinator() == m is Coordinator; +init-condition forall (m: machine) :: m in participants() == m is Participant; + +// making sure that our assumptions about pure functions are not pulled out from underneath us +invariant one_coordinator: forall (m: machine) :: m == coordinator() == m is Coordinator; +invariant participant_set: forall (m: machine) :: m in participants() == m is Participant; + +// set all the fields to their default values +init-condition forall (c: Coordinator) :: c.yesVotes == default(map[Round, set[machine]]); +init-condition forall (c: Coordinator) :: c.commited == default(set[Round]); +init-condition forall (c: Coordinator) :: c.aborted == default(set[Round]); +init-condition forall (p: Participant) :: p.commited == default(set[Round]); +init-condition forall (p: Participant) :: p.aborted == default(set[Round]); + +// make sure we never get a message that we're not expecting +invariant never_commit_to_coordinator: forall (e: event) :: e is eCommit && e targets coordinator() ==> !inflight e; +invariant never_abort_to_coordinator: forall (e: event) :: e is eAbort && e targets coordinator() ==> !inflight e; +invariant never_req_to_coordinator: forall (e: event) :: e is eVoteReq && e targets coordinator() ==> !inflight e; +invariant never_resp_to_participant: forall (e: event, p: Participant) :: e is eVoteResp && e targets p ==> !inflight e; + +// the main invariant we care about +invariant safety: forall (c: Coordinator, p1: Participant, r: Round) :: (r in p1.commited ==> (forall (p2: Participant) :: preference(p2, r) == YES)); + +// supporting invariants from non-round proof +invariant a1: forall (e: eVoteResp) :: sent e ==> e.source is Participant; +invariant a2: forall (e: eVoteResp) :: sent e ==> e.vote == preference(e.source, e.round); +invariant a3a: forall (c: Coordinator, e: eCommit) :: sent e ==> e.round in c.commited; +invariant a3b: forall (c: Coordinator, e: eAbort) :: sent e ==> e.round in c.aborted; +invariant a4: forall (c: Coordinator, r: Round, p1: Participant) :: r in p1.commited ==> r in c.commited; +invariant a5: forall (p: Participant, c: Coordinator, r: Round) :: (r in c.yesVotes && p in c.yesVotes[r]) ==> preference(p, r) == YES; +invariant a6: forall (c: Coordinator, r: Round) :: r in c.commited ==> (forall (p2: Participant) :: preference(p2, r) == YES); + +// make sure that votes have been initialized +invariant a7a: forall (c: Coordinator, e: eVoteReq) :: sent e ==> e.round in c.yesVotes; +invariant a7b: forall (c: Coordinator, e: eVoteResp) :: sent e ==> e.round in c.yesVotes; + + diff --git a/Tutorial/Advanced/2_TwoPhaseCommitVerification/Rounds/TwoPhaseCommitVerification.pproj b/Tutorial/Advanced/2_TwoPhaseCommitVerification/Rounds/TwoPhaseCommitVerification.pproj new file mode 100644 index 0000000000..f76aad3a17 --- /dev/null +++ b/Tutorial/Advanced/2_TwoPhaseCommitVerification/Rounds/TwoPhaseCommitVerification.pproj @@ -0,0 +1,8 @@ + +TwoPhaseCommitVerification + + ./PSrc/ + +./PGenerated/ +PVerifier + diff --git a/Tutorial/Advanced/2_TwoPhaseCommitVerification/Single/PSrc/System.p b/Tutorial/Advanced/2_TwoPhaseCommitVerification/Single/PSrc/System.p new file mode 100644 index 0000000000..856f35ee9c --- /dev/null +++ b/Tutorial/Advanced/2_TwoPhaseCommitVerification/Single/PSrc/System.p @@ -0,0 +1,129 @@ +type tVoteResp = (source: machine); + +event eVoteReq; +event eYes: tVoteResp; +event eNo: tVoteResp; +event eAbort; +event eCommit; + +machine Coordinator +{ + var yesVotes: set[machine]; + + start state Init { + entry { + var p: machine; + foreach (p in participants()) + invariant forall new (e: event) :: forall (m: machine) :: e targets m ==> m in participants(); + invariant forall new (e: event) :: e is eVoteReq; + { + send p, eVoteReq; + } + goto WaitForResponses; + } + } + + state WaitForResponses { + on eYes do (resp: tVoteResp) { + var p: machine; + yesVotes += (resp.source); + if (yesVotes == participants()) { + foreach (p in participants()) + invariant forall new (e: event) :: forall (m: machine) :: e targets m ==> m in participants(); + invariant forall new (e: event) :: e is eCommit; + { + send p, eCommit; + } + goto Committed; + } + } + on eNo do (resp: tVoteResp) { + var p: machine; + foreach (p in participants()) + invariant forall new (e: event) :: forall (m: machine) :: e targets m ==> m in participants(); + invariant forall new (e: event) :: e is eAbort; + { + send p, eAbort; + } + goto Aborted; + } + } + + state Committed {ignore eYes, eNo;} + state Aborted {ignore eYes, eNo;} +} + +machine Participant { + start state Undecided { + on eVoteReq do { + if (preference(this)) { + send coordinator(), eYes, (source = this,); + } else { + send coordinator(), eNo, (source = this,); + } + } + + on eCommit do { + goto Accepted; + } + + on eAbort do { + goto Rejected; + } + } + + state Accepted {ignore eVoteReq, eCommit, eAbort;} + state Rejected {ignore eVoteReq, eCommit, eAbort;} +} + +// using these to avoid initialization +pure participants(): set[machine]; +pure coordinator(): machine; +pure preference(m: machine) : bool; + +// assumptions about how the system is setup and the pure functions above +init-condition forall (m: machine) :: m == coordinator() == m is Coordinator; +init-condition forall (m: machine) :: m in participants() == m is Participant; + +// set all the fields to their default values +init-condition forall (c: Coordinator) :: c.yesVotes == default(set[machine]); + +// making sure that our assumptions about pure functions are not pulled out from underneath us +Lemma system_config { + invariant one_coordinator: forall (m: machine) :: m == coordinator() <==> m is Coordinator; + invariant participant_set: forall (m: machine) :: m in participants() <==> m is Participant; + invariant never_commit_to_coordinator: forall (e: event) :: e is eCommit && e targets coordinator() ==> !inflight e; + invariant never_abort_to_coordinator: forall (e: event) :: e is eAbort && e targets coordinator() ==> !inflight e; + invariant never_req_to_coordinator: forall (e: event) :: e is eVoteReq && e targets coordinator() ==> !inflight e; + invariant never_yes_to_participant: forall (e: event, p: Participant) :: e is eYes && e targets p ==> !inflight e; + invariant never_yes_to_init: forall (e: event, c: Coordinator) :: e is eYes && e targets c && c is Init ==> !inflight e; + invariant never_no_to_participant: forall (e: event, p: Participant) :: e is eNo && e targets p ==> !inflight e; + invariant never_no_to_init: forall (e: event, c: Coordinator) :: e is eNo && e targets c && c is Init ==> !inflight e; + invariant req_implies_not_init: forall (e: event, c: Coordinator) :: e is eVoteReq && c is Init ==> !inflight e; +} + +// the main invariant we care about +invariant safety: forall (p1: Participant) :: p1 is Accepted ==> (forall (p2: Participant) :: preference(p2)); + +// supporting invariants, based on the Kondo paper +Lemma kondo { + invariant a1a: forall (e: eYes) :: inflight e ==> e.source in participants(); + invariant a1b: forall (e: eNo) :: inflight e ==> e.source in participants(); + invariant a2a: forall (e: eYes) :: inflight e ==> preference(e.source); + invariant a2b: forall (e: eNo) :: inflight e ==> !preference(e.source); + invariant a3b: forall (e: eAbort) :: inflight e ==> coordinator() is Aborted; + invariant a3a: forall (e: eCommit) :: inflight e ==> coordinator() is Committed; + invariant a4: forall (p: Participant) :: p is Accepted ==> coordinator() is Committed; + invariant a5: forall (p: Participant, c: Coordinator) :: p in c.yesVotes ==> preference(p); + invariant a6: coordinator() is Committed ==> (forall (p: Participant) :: p in participants() ==> preference(p)); +} + +Proof system_configs { + prove system_config; + prove default using system_config; +} + +Proof kondo_proof { + prove kondo using system_config; + prove safety using kondo, system_config_participant_set; +} \ No newline at end of file diff --git a/Tutorial/Advanced/2_TwoPhaseCommitVerification/Single/TwoPhaseCommitVerification.pproj b/Tutorial/Advanced/2_TwoPhaseCommitVerification/Single/TwoPhaseCommitVerification.pproj new file mode 100644 index 0000000000..f76aad3a17 --- /dev/null +++ b/Tutorial/Advanced/2_TwoPhaseCommitVerification/Single/TwoPhaseCommitVerification.pproj @@ -0,0 +1,8 @@ + +TwoPhaseCommitVerification + + ./PSrc/ + +./PGenerated/ +PVerifier + diff --git a/Tutorial/Advanced/3_RingLeaderVerification/.gitignore b/Tutorial/Advanced/3_RingLeaderVerification/.gitignore new file mode 100644 index 0000000000..161a65f6d8 --- /dev/null +++ b/Tutorial/Advanced/3_RingLeaderVerification/.gitignore @@ -0,0 +1,7 @@ +*.out +*.err +portfolio/ +Test.cs +pom.xml +*.csproj +PCheckerOutput/ diff --git a/Tutorial/Advanced/3_RingLeaderVerification/PSrc/System.p b/Tutorial/Advanced/3_RingLeaderVerification/PSrc/System.p new file mode 100644 index 0000000000..9779f81000 --- /dev/null +++ b/Tutorial/Advanced/3_RingLeaderVerification/PSrc/System.p @@ -0,0 +1,47 @@ +type tNominate = (voteFor: machine); + +event eNominate : tNominate; + +pure le(x: machine, y: machine): bool; +axiom forall (x: machine) :: le(x, x); +axiom forall (x: machine, y: machine) :: le(x, y) || le(y, x); +axiom forall (x: machine, y: machine, z: machine) :: (le(x, y) && le(y, z)) ==> le(x, z); +axiom forall (x: machine, y: machine) :: (le(x, y) && le(y, x)) ==> (x == y); + +pure btw(x: machine, y: machine, z: machine): bool; +axiom forall (w: machine, x: machine, y: machine, z: machine) :: (btw(w, x, y) && btw(w, y, z)) ==> btw(w, x, z); +axiom forall (x: machine, y: machine, z: machine) :: btw(x, y, z) ==> !btw(x, z, y); +axiom forall (x: machine, y: machine, z: machine) :: btw(x, y, z) || btw(x, z, y) || (x == y) || (x == z) || (y == z); +axiom forall (x: machine, y: machine, z: machine) :: btw(x, y, z) ==> btw(y, z, x); + +pure right(x: machine): machine; +axiom forall (x: machine) :: x != right(x); +axiom forall (x: machine, y: machine) :: (x != y && y != right(x)) ==> btw(x, right(x), y); + +machine Server { + start state Proposing { + entry { + send right(this), eNominate, (voteFor=this,); + } + on eNominate do (n: tNominate) { + if (n.voteFor == this) { + goto Won; + } else if (le(this, n.voteFor)) { + send right(this), eNominate, (voteFor=n.voteFor,); + } else { + send right(this), eNominate, (voteFor=this,); + } + } + } + state Won { + ignore eNominate; + } +} + +// voteFor is the running max +invariant NoBypass: forall (n: machine, m: machine, e: eNominate) :: (inflight e && e targets m && n is Server && btw(e.voteFor, n, m)) ==> le(n, e.voteFor); +invariant SelfPendingMax: forall (n: machine, m: machine, e: eNominate) :: (inflight e && e targets m && e.voteFor == m) ==> le(n, m); + +// Main theorems +invariant LeaderMax: forall (x: machine, y: machine) :: x is Won ==> le(y, x); +invariant UniqueLeader: forall (x: machine, y: machine) :: (x is Won && y is Won) ==> (le(x, y) && le(y, x)); \ No newline at end of file diff --git a/Tutorial/Advanced/3_RingLeaderVerification/RingLeaderVerification.pproj b/Tutorial/Advanced/3_RingLeaderVerification/RingLeaderVerification.pproj new file mode 100644 index 0000000000..b121122658 --- /dev/null +++ b/Tutorial/Advanced/3_RingLeaderVerification/RingLeaderVerification.pproj @@ -0,0 +1,8 @@ + +RingLeaderVerification + + ./PSrc/ + +./PGenerated/ +PVerifier + diff --git a/Tutorial/Advanced/4_Paxos/PSrc/System.p b/Tutorial/Advanced/4_Paxos/PSrc/System.p new file mode 100644 index 0000000000..cacc8b61a0 --- /dev/null +++ b/Tutorial/Advanced/4_Paxos/PSrc/System.p @@ -0,0 +1,394 @@ +// PVerifier Paxos Consensus Protocol Implementation +// ============================================================================ +// TYPE DEFINITIONS +// ============================================================================ + +type tProposal = (id: int, value: int); +type tPrepareReq = (proposalId: int, proposer: Proposer); +type tPromiseResp = (proposalId: int, acceptedId: int, acceptedValue: int, acceptor: Acceptor); +type tAcceptReq = (proposalId: int, value: int, proposer: Proposer); +type tAcceptedResp = (proposalId: int, value: int, acceptor: Acceptor); + +// ============================================================================ +// EVENT DEFINITIONS +// ============================================================================ + +// Phase 1: Prepare/Promise +event ePrepare: tPrepareReq; +event ePromise: tPromiseResp; +event eReject: (proposalId: int, acceptor: Acceptor); + +// Phase 2: Accept/Accepted +event eAccept: tAcceptReq; +event eAccepted: tAcceptedResp; + +// Learning and coordination +event eChosen: (value: int, proposalId: int, proposer: Proposer); +event ePropose: (value: int); + +// ============================================================================ +// PURE FUNCTIONS (System Configuration) +// ============================================================================ + +pure proposers(): set[machine]; +pure acceptors(): set[machine]; +pure learners(): set[machine]; +pure isQuorum(s: set[machine]): bool; +pure proposalId(p: machine): int; +init-condition forall (m1: Proposer, m2: Proposer) :: (m1 == m2) == (proposalId(m1) == proposalId(m2)); + +// ============================================================================ +// PROPOSER MACHINE +// ============================================================================ + +machine Proposer { + var currentProposalId: int; + var proposedValue: int; + var promiseSet: set[machine]; + var acceptSet: set[machine]; + var highestAcceptedId: int; + var highestAcceptedValue: int; + + start state Idle { + on ePropose do (payload: (value: int)) { + var a: machine; + proposedValue = payload.value; + promiseSet = default(set[machine]); + acceptSet = default(set[machine]); + highestAcceptedId = -1; + highestAcceptedValue = -1; + assume currentProposalId == proposalId(this); + assume forall (m: Proposer) :: m != this ==> m.currentProposalId != currentProposalId; + + // Phase 1: Send prepare to all acceptors + foreach (a in acceptors()) + invariant forall new (e: event) :: e is ePrepare; + invariant currentProposalId == proposalId(this); + invariant forall (m: Proposer) :: m != this ==> m.currentProposalId != currentProposalId; + invariant forall new (e: ePrepare) :: e.proposalId == currentProposalId && e.proposer == this; + invariant forall new (e: event) :: forall (m: machine) :: e targets m ==> m in acceptors(); + { + send a, ePrepare, (proposalId = currentProposalId, proposer = this); + } + goto Preparing; + } + ignore ePromise, eReject, eAccepted, eChosen; + } + + state Preparing { + on ePromise do (resp: tPromiseResp) { + var a: machine; + var valueToPropose: int; + assume currentProposalId == proposalId(this); + assume forall (m: Proposer) :: m != this ==> m.currentProposalId != currentProposalId; + if (resp.proposalId == currentProposalId) { + promiseSet += (resp.acceptor); + + // Track highest accepted proposal + if (resp.acceptedId > highestAcceptedId) { + highestAcceptedId = resp.acceptedId; + highestAcceptedValue = resp.acceptedValue; + } + + // If we have quorum promises, move to Phase 2 + if (isQuorum(promiseSet)) { + if (highestAcceptedId >= 0) { + valueToPropose = highestAcceptedValue; + } else { + valueToPropose = proposedValue; + } + + proposedValue = valueToPropose; + + // Phase 2: Send accept to all acceptors + foreach (a in acceptors()) + invariant forall new (e: event) :: e is eAccept; + invariant isQuorum(promiseSet); + invariant forall (m: Proposer) :: m != this ==> m.currentProposalId != currentProposalId; + invariant forall new (e1: eAccept) :: forall new (e2: eAccept) :: e1.proposalId == e2.proposalId && e1.value == e2.value && e1.proposer == e2.proposer; + invariant forall new (e: eAccept) :: e.proposalId == currentProposalId && e.value == valueToPropose && e.proposer == this; + invariant forall new (e: event) :: forall (m: machine) :: e targets m ==> m in acceptors(); + { + send a, eAccept, (proposalId = currentProposalId, value = valueToPropose, proposer = this); + } + goto Proposing; + } + } + } + + on eReject do (resp: (proposalId: int, acceptor: machine)) { + goto Done; + } + + ignore eAccepted, eChosen, ePropose; + } + + state Proposing { + on eAccepted do (resp: tAcceptedResp) { + var l: machine; + if (resp.proposalId == currentProposalId) { + acceptSet += (resp.acceptor); + + // If quorum accepted, notify learners + if (isQuorum(acceptSet)) { + foreach (l in learners()) + invariant isQuorum(acceptSet); + invariant forall new (e: event) :: e is eChosen; + invariant forall new (e: eChosen) :: e.value == resp.value && e.proposalId == resp.proposalId && e.proposer == this; + invariant forall new (e: eChosen) :: exists (a: eAccepted) :: sent a && e.value == a.value && e.proposalId == a.proposalId; + invariant forall new (e: event) :: forall (m: machine) :: e targets m ==> m in learners(); + { + send l, eChosen, (value=resp.value, proposalId=resp.proposalId, proposer=this); + } + goto Chosen; + } + } + } + + ignore ePromise, eReject, ePropose; + } + + state Chosen { + ignore ePromise, eReject, eAccepted, eChosen, ePropose; + } + + state Done { + ignore ePromise, eReject, eAccepted, eChosen, ePropose; + } +} + +// ============================================================================ +// ACCEPTOR MACHINE +// ============================================================================ + +machine Acceptor { + var highestPrepare: int; + var acceptedProposal: tProposal; + + start state Waiting { + entry { + highestPrepare = -1; + acceptedProposal = (id = -1, value = -1); + } + + on ePrepare do (req: tPrepareReq) { + if (req.proposalId > highestPrepare) { + highestPrepare = req.proposalId; + send req.proposer, ePromise, ( + proposalId = req.proposalId, + acceptedId = acceptedProposal.id, + acceptedValue = acceptedProposal.value, + acceptor = this + ); + } else { + send req.proposer, eReject, (proposalId = req.proposalId, acceptor = this); + } + } + + on eAccept do (req: tAcceptReq) { + if (req.proposalId >= highestPrepare) { + acceptedProposal = (id = req.proposalId, value = req.value); + send req.proposer, eAccepted, ( + proposalId = req.proposalId, + value = req.value, + acceptor = this + ); + } + } + + ignore eChosen, ePropose; + } +} + +// ============================================================================ +// LEARNER MACHINE +// ============================================================================ + +machine Learner { + var chosenValue: int; + + start state Learning { + entry { + chosenValue = -1; + } + + on eChosen do (payload: (value: int, proposalId: int, proposer: Proposer)) { + if (chosenValue == -1) { + chosenValue = payload.value; + goto Learned; + } + } + + ignore ePrepare, ePromise, eReject, eAccept, eAccepted; + } + + state Learned { + ignore ePrepare, ePromise, eReject, eAccept, eAccepted, eChosen; + } +} + +// ============================================================================ +// INITIALIZATION CONDITIONS +// ============================================================================ + +init-condition forall (m: machine) :: m in proposers() == m is Proposer; +init-condition forall (m: machine) :: m in acceptors() == m is Acceptor; +init-condition forall (m: machine) :: m in learners() == m is Learner; + +// Quorum axioms - any two quorums must intersect +init-condition forall (q1: set[machine], q2: set[machine]) :: + isQuorum(q1) && isQuorum(q2) ==> exists (a: machine) :: a in q1 && a in q2; + +// Quorum must be subset of acceptors +init-condition forall (q: set[machine]) :: + isQuorum(q) ==> forall (a: machine) :: a in q ==> a in acceptors(); + +// Non-empty acceptor set has at least one quorum +init-condition acceptors() != default(set[machine]) ==> + exists (q: set[machine]) :: isQuorum(q); + +// Initialize acceptor state +init-condition forall (a: Acceptor) :: a.highestPrepare == -1; +init-condition forall (a: Acceptor) :: a.acceptedProposal.id == -1; + +// Initialize proposer state +init-condition forall (p: Proposer) :: p.currentProposalId == proposalId(p); +invariant id_unchange: forall (p: Proposer) :: p.currentProposalId == proposalId(p); +init-condition forall (p: Proposer) :: p.promiseSet == default(set[machine]); +init-condition forall (p: Proposer) :: p.acceptSet == default(set[machine]); + +// non-empty acceptor and proposer sets +init-condition exists (p: Proposer) :: p in proposers(); +init-condition exists (a: Acceptor) :: a in acceptors(); +// non-empty learner set +init-condition exists (l: Learner) :: l in learners(); + +// Initialize learner state +init-condition forall (l: Learner) :: l.chosenValue == -1; + +// ============================================================================ +// SYSTEM CONFIGURATION INVARIANTS +// ============================================================================ + +Lemma system_config { + // Machine type invariants + invariant proposer_set: forall (m: machine) :: m in proposers() == m is Proposer; + invariant acceptor_set: forall (m: machine) :: m in acceptors() == m is Acceptor; + invariant learner_set: forall (m: machine) :: m in learners() == m is Learner; + + // Quorum properties + invariant quorum_intersection: forall (q1: set[machine], q2: set[machine]) :: + isQuorum(q1) && isQuorum(q2) ==> exists (a: machine) :: a in q1 && a in q2; + invariant quorum_subset: forall (q: set[machine]) :: + isQuorum(q) ==> forall (a: machine) :: a in q ==> a in acceptors(); + + invariant proposedIdUnique: + forall (m1: Proposer, m2: Proposer) :: (m1 == m2) == (proposalId(m1) == proposalId(m2)); + + // Message routing invariants + invariant prepare_to_acceptors: forall (e: event) :: e is ePrepare ==> + forall (m: machine) :: e targets m ==> m in acceptors(); + invariant promise_to_proposers: forall (e: event) :: e is ePromise ==> + forall (m: machine) :: e targets m ==> m in proposers(); + invariant reject_to_proposers: forall (e: event) :: e is eReject ==> + forall (m: machine) :: e targets m ==> m in proposers(); + invariant accept_to_acceptors: forall (e: event) :: e is eAccept ==> + forall (m: machine) :: e targets m ==> m in acceptors(); + invariant accepted_to_proposers: forall (e: event) :: e is eAccepted ==> + forall (m: machine) :: e targets m ==> m in proposers(); + invariant chosen_to_learners: forall (e: event) :: e is eChosen ==> + forall (m: machine) :: e targets m ==> m in learners(); + invariant no_propose_to_learners: + forall (e: ePropose, l: Learner) :: e targets l ==> !inflight e; + invariant eChosen_implies_in_chosen: + forall (e: eChosen) :: sent e ==> e.proposer is Chosen; + invariant in_chosen_implies_quorum_accept: + forall (m: Proposer) :: m is Chosen ==> isQuorum(m.acceptSet); + + // Proposer behaviors + invariant proposer_id_correspond: + forall (e: eAccept) :: sent e ==> e.proposalId == e.proposer.currentProposalId && e.value == e.proposer.proposedValue; + invariant preparing_means_not_proposed: + forall (m: Proposer) :: m is Preparing || m is Idle ==> forall (e: eAccept) :: e.proposer != m && e.proposalId != m.currentProposalId; + invariant proposed_means_not_preparing: + forall (e: eAccept) :: sent e ==> (e.proposer is Proposing || e.proposer is Chosen || e.proposer is Done); + invariant unique_proposal: + forall (e1: eAccept, e2: eAccept) :: sent e1 && sent e2 && e1.proposalId == e2.proposalId ==> e1.value == e2.value; +} + +// ============================================================================ +// PAXOS SAFETY INVARIANTS +// ============================================================================ + +// Lemmas +Lemma accepted_value_corresponds_to_proposed { + invariant accepted_value_corres: + forall (e: eAccepted, p: Proposer) :: sent e && e targets p && e.proposalId == p.currentProposalId ==> e.value == p.proposedValue; + invariant accepted_means_proposed: + forall (e1: eAccepted, p: Proposer) :: sent e1 && e1 targets p ==> + exists (e2: eAccept) :: sent e2 && e2 targets e1.acceptor && e2.proposer == p + && e2.proposalId == e1.proposalId + && e2.value == e1.value + && p.proposedValue == e1.value; +} +Proof Lem_accepted_value_corresponds_to_proposed { + prove accepted_value_corresponds_to_proposed using system_config; +} + +Lemma chosen_means_quorum_accept { + invariant chosen_accepted: + forall (e1: eChosen) :: sent e1 ==> isQuorum(e1.proposer.acceptSet); + invariant chosen_value_proposed: + forall (e1: eChosen) :: sent e1 ==> e1.value == e1.proposer.proposedValue; +} +Proof Lem_chosen_means_quorum_accept { + prove chosen_means_quorum_accept using system_config, accepted_value_corresponds_to_proposed; +} + +Lemma quorum_accept_implies_quorum_promise { + invariant quorum_accepts: + forall (p: Proposer) :: isQuorum(p.acceptSet) ==> isQuorum(p.promiseSet); + invariant quorum_accept_implies_received_accepted: + forall (p: Proposer) :: isQuorum(p.acceptSet) ==> exists (e: eAccepted) :: sent e && e targets p; + invariant received_accepted_implies_accept_sent: + forall (e: eAccepted, p: Proposer) :: sent e && e targets p ==> exists (e1: eAccept) :: sent e1 && e1.proposer == p; + invariant accept_sent_implies_quorum_promise: + forall (e: eAccept, p: Proposer) :: sent e && e.proposer == p ==> isQuorum(p.promiseSet); +} +Proof Lem_quorum_accept_implies_quorum_promise { + prove quorum_accept_implies_quorum_promise using system_config; +} + +Lemma quorum_promise_same_value { + invariant quorum_promise_implies_same_value: + forall (p1: Proposer, p2: Proposer) :: + isQuorum(p1.promiseSet) && isQuorum(p2.promiseSet) ==> + p1.proposedValue == p2.proposedValue; +} +Proof Lem_quorum_accepts_and_quorum_promise { + prove quorum_promise_same_value using system_config; +} + +Lemma two_quorum_accept_implies_same_value { + invariant two_quorum_accepts: + forall (p1: Proposer, p2: Proposer) :: + isQuorum(p1.acceptSet) && isQuorum(p2.acceptSet) ==> + p1.proposedValue == p2.proposedValue; +} +Proof Lem_two_quorum_accept_implies_same_value { + prove two_quorum_accept_implies_same_value using + system_config, quorum_promise_same_value, quorum_accept_implies_quorum_promise; +} + +// ============================================================================ +// THEOREM: unique_chosen_value +// ============================================================================ +Theorem unique_chosen_value { + invariant unique_learned_value: + forall (e1: eChosen, e2: eChosen) :: sent e1 && sent e2 ==> e1.value == e2.value; +} +Proof PaxosSafety { + prove system_config; + prove unique_chosen_value using system_config, chosen_means_quorum_accept, two_quorum_accept_implies_same_value; + prove default using system_config; +} diff --git a/Tutorial/Advanced/4_Paxos/PVerifiedPaxos.pproj b/Tutorial/Advanced/4_Paxos/PVerifiedPaxos.pproj new file mode 100644 index 0000000000..c71f361ca2 --- /dev/null +++ b/Tutorial/Advanced/4_Paxos/PVerifiedPaxos.pproj @@ -0,0 +1,8 @@ + +PVerifiedPaxos + + ./PSrc/ + +./PGenerated/ +PVerifier + diff --git a/Tutorial/Advanced/5_Consensus/Consensus.pproj b/Tutorial/Advanced/5_Consensus/Consensus.pproj new file mode 100644 index 0000000000..fd33e3a6d8 --- /dev/null +++ b/Tutorial/Advanced/5_Consensus/Consensus.pproj @@ -0,0 +1,8 @@ + +Consensus + + ./PSrc/ + +./PGenerated/ +PVerifier + diff --git a/Tutorial/Advanced/5_Consensus/PSrc/System.p b/Tutorial/Advanced/5_Consensus/PSrc/System.p new file mode 100644 index 0000000000..8b394d156c --- /dev/null +++ b/Tutorial/Advanced/5_Consensus/PSrc/System.p @@ -0,0 +1,68 @@ +event eRequestVote: (src: Node); +event eVote: (voter: Node); + +pure nodes(): set[machine]; +pure isQuorum(s: set[machine]): bool; + +init-condition forall (m: machine) :: m in nodes() == m is Node; +axiom forall (q1: set[machine], q2: set[machine]) :: + isQuorum(q1) && isQuorum(q2) ==> exists (a: machine) :: a in q1 && a in q2; +axiom forall (q: set[machine]) :: + isQuorum(q) ==> forall (a: machine) :: a in q ==> a in nodes(); + +init-condition forall (n: Node) :: !n.voted && n.votes == default(set[machine]); + +machine Node { + var voted: bool; + var votes: set[machine]; + + start state RequestVoting { + entry { + var m: machine; + foreach (m in nodes()) + invariant forall new (e: event) :: e is eRequestVote; + invariant forall new (e: eRequestVote) :: e.src == this; + { + send m, eRequestVote, (src=this,); + } + } + + on eRequestVote do (payload: (src: Node)) { + if (!voted) { + voted = true; + send payload.src, eVote, (voter=this,); + } + } + + on eVote do (payload: (voter: Node)) { + votes += (payload.voter); + if (isQuorum(votes)) { + goto Won; + } + } + + } + + state Won { + ignore eRequestVote, eVote; + } +} + +Lemma quorum_votes { + invariant one_vote_per_node: + forall (e1: eVote, e2: eVote) :: e1.voter == e2.voter ==> e1 == e2; + invariant won_implies_quorum_votes: + forall (n: Node) :: n is Won ==> isQuorum(n.votes); +} +Proof { + prove quorum_votes; +} + +Theorem election_safety { + invariant unique_leader: + forall (n1: Node, n2: Node) :: n1 is Won && n2 is Won ==> n1 == n2; +} +Proof { + prove election_safety using quorum_votes; + prove default; +} diff --git a/Tutorial/Advanced/6_DistributedLock/DistributedLock.pproj b/Tutorial/Advanced/6_DistributedLock/DistributedLock.pproj new file mode 100644 index 0000000000..05cf6445d7 --- /dev/null +++ b/Tutorial/Advanced/6_DistributedLock/DistributedLock.pproj @@ -0,0 +1,8 @@ + +DistributedLock + + ./PSrc/ + +./PGenerated/ +PVerifier + diff --git a/Tutorial/Advanced/6_DistributedLock/PSrc/System.p b/Tutorial/Advanced/6_DistributedLock/PSrc/System.p new file mode 100644 index 0000000000..79f8a2d7d5 --- /dev/null +++ b/Tutorial/Advanced/6_DistributedLock/PSrc/System.p @@ -0,0 +1,41 @@ +event eGrant: (node: Node, epoch: int); +event eAccept: (epoch: int, source: Node); + +machine Node { + var epoch: int; + var held: bool; + + start state Act { + on eGrant do (payload: (node: Node, epoch: int)) { + if (held && payload.epoch > epoch) { + held = false; + send payload.node, eAccept, (epoch=payload.epoch, source=this); + } + } + + on eAccept do (payload: (epoch: int, source: Node)) { + if (payload.epoch > epoch) { + held = true; + epoch = payload.epoch; + } + } + } +} + +init-condition exists (n: Node) :: n.held && n.epoch > 0 && forall (n1: Node) :: n1 != n ==> !n1.held && n1.epoch == 0; + +Theorem safety { + invariant unique_holder: forall (n1: Node, n2: Node) :: n1.held && n2.held ==> n1 == n2; + invariant no_lock_while_transfer: + forall (n: Node, e: eAccept) :: inflight e ==> !n.held; + invariant unique_accept: + forall (e1: eAccept, e2: eAccept) :: inflight e1 && inflight e2 ==> e1 == e2; + invariant not_held_after_release: + forall (n1: Node, e: eAccept) :: inflight e && e.source == n1 ==> !n1.held; + invariant transfer_to_higher: + forall (n1: Node, n2: Node, e: eAccept) :: inflight e && e.source == n1 && e targets n2 ==> e.epoch > n1.epoch; +} +Proof Safety { + prove safety; + prove default; +} \ No newline at end of file