In the class, we've discussed the Hoare logic, an axiomatic semantics for formal reasoning about program correctness. One important application of Hoare logic is in the field of program verification, where the desired properties of the programs can be reasoned formally and rigorously. To be specific, there are two different styles to reason about the program: the backward style, which scans the program in a reverse order and is suitable for the structured programs; and the forward style, which scans the program from the beginning to the end, and is suitable for either the structured or unstructured programs.
In this assignment, you'll implement a Hoare logic system by completing both a backward and a forward verification systems. To be specific, this assignment is divided into two parts, each of which contains both some tutorials and problems. The first part covers a backward verification system, which operates on the aforementioned language C--; and the second part covers a forward verification system which operates on a low-level assembly-like intermediate language TAC, which is a modified version of the Three Address Code. Strictly speaking, these two systems are equivalent in their expressiveness, but having both of them will get you more familiarized with the different verification technologies on different flavors of languages. Some problems are tagged with Exercise, which you should solve. And several problems are tagged with Challenge, which are optional. Download code templates to start with.
Before starting with this assignment, make sure you've finished Software Setup in the lab 1, and have Z3 and Python(3.12+ recommended) properly installed on your computer. For any problems, please feel free to contact us for help.
When you finished the lab, zip you code files with file name <ID>-lab-8.zip (e.g., SA19225789-lab-8.zip), and submit it to USTC Online Teaching Platform. Any late submission will NOT be accepted.
The first verifier you'll be building is of backward style, and operates on the C-- language. Below is the syntax of C--, with some modifications from last assignment: we add the implication ->, and && , or ||, and negation ~:
bop ::= + | - | * | / | == | != | > | < | >= | <= | -> | && | || E ::= n | x | E bop E | ~E S ::= skip | x=E | S;S | f(E1, ..., En) | if(E, S, S) | while I(E, S) F ::= f(x1, ..., xn){S; return E;}
As with last assignment, we need to build a group of abstract syntax tree data structures to represent the C-- syntax. We have supplied you these data structures, which you should first get yourself familiarized of.
We've discussed the backward verification condition generation rules in the class, which are repeated below for your reference:
VC(skip, P) = P VC(S1;S2, P) = VC(S1, VC(S2, P)) VC(x=e, P) = P[x↦e] VC(if(e;s1;s2), P) = (e → VC(s1, P))∧(~e → VC(s2, P)) VC(while I(e;s), P) = I ∧ (∀x1 x2 ... xn.I →(e → VC(s, I) ∧ (~e → P)))
Pay special attention to the universal quantifier in the rule for the while statement: the variables x1, x2, ..., and xn are assigned in the while body. Basically, this rule requires the while statement is annotated with the modified variables in the loop body. This StmWhile class record this information in the modified_vars field.
As usual, after generating the verification conditions, we continue to convert them to Z3 format, and send it to the Z3 solver to prove it. If Z3 proves the VC, the target program is verified, or else if Z3 reports some contradiction, the verification fails. The reason is either the program is wrong, or the supplied specification does not specify the programs correctly.
As we discussed in the class, for target programs with many if statement, the generated verification conditions can grow exponentially, this is called verification condition explosion. This brings several challenges to the verification system implementation: first, the time spent in the generation tends to be longer; second, the generated verification condition tends to be larger; finally, the generated VC may be beyond the capability of modern solvers.
$ python3 gen_prog.py 10to generate a test file, where 10 is number of statements you want to generate, you can change it to try other values. This command will generate a file large_prog.py which contains a test program. Run the generated program, and be sure to answer the following problems:
In this part of the assignment, we'll build a forward verification generator, which scans the program from the beginning to the end. The generator you'll build work on an unstructured language: TAC, which is an abbreviation of Three Address Code, though the version we'll be using is a variant of the standard one. The key difference is that the expression we'll be using are compound, instead of atomic. Though the TAC language here is not suitable for program analysis and optimization, it's enough for our verification purpose in this assignment.
The intermediate language we'll be using is given by the following syntax:
bop ::= + | - | * | / | == | != | > | < | >= | <= | -> | && | || E ::= n | x | E bop E | ~E S ::= skip // empty | x=E // assignment | L: // label | inv E // an invariant E | f(E1, …, En) // function call | if(E, L1, L2) // branching | goto L // jump | return E // function return F ::= f(x1, …, xn){S*}
Control flows are not structured any more, but only explicit labels and jumps.
In principal, we can also write down the TAC programs manually, making use of the data structures, as we've done for the C-- programs. However, doing so will be painfully and tedious, due to the low-level nature of the TAC language. Luckily, there is a more convenient and easier way: to compile the C-- programs directly down to equivalent TAC program. We have supplied the code for the compiler to you.
As in the backward generator, we can send the generated VC to a prover/solver to prove it, as described by the following process:
programs ---> verification ---> verification ---> solver ---> prove/disprove condition generator condition
from z3 import * ctx = Context(proof=True) solver = Solver(ctx=ctx) x = Bool('x', ctx=ctx) solver.add(Not(Implies(x, x))) res = solver.check() print(res) proof = solver.proof() print(proof)
This completes this assignment. Remember to zip you homework with file name <ID>-lab-8.zip (e.g., SA19225789-lab-8.zip), and submit it to USTC Online Teaching Platform.
Happy hacking!