Core Language

The following is an incomplete grammar of the core language.

Spec_var ::= string ;

Ident ::= string ;

Type ::= string ;

Ghost ::= | "assert" Formula ; assertion
          | "dprint" ; print current state (?)
          | "unfold" Spec_var ; unfold specification variables

Ident_tuple ::= "()" | "(" Ident ("," Ident)* ")"

Expr ::= | integer ; integer constant
         | float ; float constant
         | "true" | "false" ; boolean constant
         | "if" Ident "[" Expr "]" "else" "[" Expr "]" ; conditional
         | "bind" Ident "to" Ident_tuple "in" Expr ; bind
         | Ident Ident_tuple ; function application
         | Ident "=" Expr ; assignment
         | Expr ";" Expr ; sequence
         | Ghost ; ghost expressions
         | "{" Expr "}" ; block
         | Type " " Ident ; variable declaration
         | "(" Type ")" Expr ; type casting (?)
         | "debug on" | "debug off" ; turning on or off devel debug
         | "while" (" Ident ")" Expr ; loop
         | "try" Expr "catch" "(" Type " " Ident ")" Expr ; try-catch

(Note that for bind, the number of bound variables must be equal to the number of fields in the data declaration.)

Some important files

  • solver.ml
  • typechecker.ml
  • astsimp.ml

General Flow

Input file -- Parse --> Input Ast (Iast) -- Normalization --> Iast -- Translation --> Core Ast (Cast) -- Forward verification --> Cast -- Entailment checking --> Done.

Normalization

Relevant files : astsimp.ml
Relevant functions : case_normalize_program, case_normalize_proc, case_normalize_struc_formula

The following is a non-exhaustive list of normalization performed:

  • While loops.
while Cond { Body }
->
let v_bool = Cond in
while v_bool { Body }
  • Function application.
function_ident(expresssion_1,...,expression_n)
->
let v1 = expression_1 in
...
let vn = expression_n in
function_ident(v1,...,vn)

(Note the left to right evaluation of arguments)

Translation

Relevant files : astsimp.ml
Relevant functions : trans_prog, trans_proc, trans_views, trans_axiom, trans_I2C_struc_formula, trans_exp

To have simpler rules for forward verification, HIPSLEEK first performs some transformation on the Iast before translating it to Cast which is a simplified version of Iast.

The following is a non-exhaustive list of transformation performed:

  • Transform while statements to tail recursive top level functions.
while Cond { Body }
->
while_rec = if Cond [ Body; while_rec ]
            else []

(Note that while_rec is the new tail recursive function introduced during transformation.)

  • Transform break/continue to try-catch block.
while Cond {... break; ...}
->
while_rec =
try ( if Cond [ try { ... throw brk_default ... }
                catch (cnt_default) { };
                while_rec ]
     else [] )
catch (brk_default) { }
while Cond {... continue; ...}
->
while_rec =
try ( if Cond [ try { ... throw cnt_default ... }
                catch (cnt_default) { };
                while_rec ]
     else [] )
catch (brk_default) { }
  • Transform field accesses to bind.
x.f_i
->
bind x to (v_1,...,v_i,...,v_n) in v_i
x.f_i = v
->
bind x to (v_1,...,v_i,...,v_n) in v_i = v
  • Transform primitive operations to function calls.
x + y
->
add___$int~int(x, y)

(Note that these primitive functions are defined in prelude.ss.)

These transformations are done in trans_exp before translating to Cast.

Forward verification

Relevant files : typechecker.ml
Relevant functions : check_proc, check_specs_infer, check_exp

check_proc is applied to every procedure/function in the program to be verified.

check_exp essentially implements the forward verification rules. It will initialize a proof state (Cformula.list_failesc_context) that contains the pre condition of the function and walk through the body of the function. While walking through the body of the function, it will update the proof state according to the forward verification rules and the kind of expression in the body.

If there is a function call within the body of the function, check_exp will check whether the proof state at the call site entails the pre condition of the function being called. If it does, then the proof state after the call will be the composition of the residue from the entailment checking and the post condition of the function being called.

Entailment checking

Relevant files : typechecker.ml, solver.ml
Relevant functions : check_proc, check_specs_infer, check_post, heap_entail_struc_list_partial_context_init

After check_exp returns the final proof state after walking through the entire body of the function, check_post will be called with the final proof state. It will then call heap_entail_struc_list_partial_context_init which will check whether the final proof state entails the post condition of the function. It it does, then the verification of the function succeeds. Otherwise, verification of the function fails.

Pure logic verification conditions

Relevant files : smtsolver.ml, z3.ml

Currently, by default, the prover discharges pure logic conditions to Z3 via invoking the instance present on the PATH. Each formula is translated into an equivalent statement in SMTLIB format, and the entailment (along with all custom relations, axioms, etc.) is verified via Z3. Each possible return value (sat, unsat, unknown) is then converted into either a proof success, or indicator of a may-failure or must-failure (indicating an unknown result, or a known invalid entailment, respectively.)

For some pure values, some type information is necessary to properly generate monomorphic SMT statements. Although type checking is done earlier in the process, (in typeinfer.ml during the Iast-to-Cast lowering, and the aforementioned typechecker.ml), the AST itself only contains partial type information (e.g. in spec var annotations.) cpure_ast_typeinfer.ml attempts to extend these annotations to something more fully usable by the SMT generator.

API docs


Limitations of API/ Possible improvements

  • Ghost statements (e.g. assert)
  • OOP
  • Termination checking
  • Better handling of failed proving than just throwing an exception

Miscellaneous


Q: What is a view?

A: Views are predicates.

Q: What is Iast.proc_decl.proc_ho_arg?

A: Higher order arguments. They are currently not supported in the api. Check out rho/web/cdl-ex3-fm.ss for example of these higher order arguments. They are variables prefixed with % symbol. (e.g. %P).

Q: What is the difference between dynamic and static specs?

A: Dynamic specs are used for OOP where dynamic binding is used to resolve method calls at runtime and therefore function specs. Static specs should be used most of the time.

**Q: What are instance call (Cast.ICall) and static calls (Cast.SCall)?

A: Instance calls are used for method calls in OOP and static calls are used for both static method calls in OOP and regular function calls.

Q: What is para continuation analysis?

A: Continuation analysis for views. To find out the continuation parameters for each view in the program and insert them into the Cast.view_cont_vars field of Cast.view_decl. Examples for views with continuation parameters are WFSegN and WFSeg in prelude.ss.

Q: What does Astsimp.compute_view_x_formula do?

A: Uses Xpure approximation on predicate to compute predicate invariant.