Skip to content

Commit

Permalink
motoko-san: support immutable records
Browse files Browse the repository at this point in the history
  • Loading branch information
DK318 committed Jun 26, 2024
1 parent 793b832 commit ae07bc1
Show file tree
Hide file tree
Showing 10 changed files with 899 additions and 12 deletions.
11 changes: 5 additions & 6 deletions src/viper/pretty.ml
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,12 @@ and pp_adt_cons ppf cons =
and pp_adt_con ppf con =
fprintf ppf "%s@[(%a)@]"
con.con_name.it
(pp_print_list ~pp_sep:comma pp_adt_con_field) (List.mapi (fun i fld -> con, i, fld) con.con_fields)
(pp_print_list ~pp_sep:comma pp_adt_con_field) con.con_fields

and pp_adt_con_field ppf (con, i, con_field) =
fprintf ppf "%s$%s : %a"
con.con_name.it
(string_of_int i)
pp_typ con_field
and pp_adt_con_field ppf (field_name, field_type) =
fprintf ppf "%s : %a"
field_name.it
pp_typ field_type

and pp_block_opt ppf = function
| None -> ()
Expand Down
2 changes: 1 addition & 1 deletion src/viper/syntax.ml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ and item' =
| MethodI of id * par list * par list * exp list * exp list * seqn option
| InvariantI of string * exp

and adt_con = { con_name : id; con_fields : typ list }
and adt_con = { con_name : id; con_fields : (id * typ) list }

and par = id * typ

Expand Down
97 changes: 92 additions & 5 deletions src/viper/trans.ml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ module Arrange = Mo_def.Arrange
module String_map = Env.Make(String)
module Stamps = String_map

module Type_map = Env.Make (T.Ord)

(* symbol generation *)

let stamps : int Stamps.t ref = ref Stamps.empty
Expand Down Expand Up @@ -107,6 +109,7 @@ type ctxt =
label_to_tmp_var : id String_map.t; (* Motoko label -> associated tmp variable *)
label_to_vpr_label : string String_map.t; (* Motoko label -> Viper label *)
reqs : reqs; (* requirements for the prelude *)
type_to_record_ctor : id Type_map.t;
ghost_items : (ctxt -> item) list ref;
ghost_inits : (ctxt -> seqn') list ref;
ghost_perms : (ctxt -> Source.region -> exp) list ref;
Expand Down Expand Up @@ -192,6 +195,25 @@ let rec strip_mut_t (t : T.typ) : T.typ =
| T.Mut t' -> strip_mut_t t'
| _ -> t

let record_ctor_tag = "$RecordCtor_"

let mk_record_ctor (typ_id : M.typ_id) : id =
let name = Format.asprintf "%s%s" record_ctor_tag typ_id.it in
!!! Source.no_region name

let get_record_name ctxt (typ : T.typ) : string =
let tag_len = String.length record_ctor_tag in
let record_ctor_name = (Type_map.find typ ctxt.type_to_record_ctor).it in
String.sub record_ctor_name tag_len (String.length record_ctor_name - tag_len)

let mk_record_field ~record_name ~fld_name =
Format.asprintf "$%s$%s" record_name fld_name

let get_record_field ctxt (typ : T.typ) (fld : M.id) : M.id =
let record_name = get_record_name ctxt typ in
let fld_name = mk_record_field ~record_name ~fld_name:fld.it in
{ fld with it = fld_name }

let rec unit reqs (u : M.comp_unit) : prog Diag.result =
Diag.(
reset_stamps();
Expand All @@ -218,6 +240,7 @@ and unit' reqs (u : M.comp_unit) : prog =
label_to_tmp_var = String_map.empty;
label_to_vpr_label = String_map.empty;
reqs = reqs;
type_to_record_ctor = Type_map.empty;
ghost_items = ref [];
ghost_inits = ref [];
ghost_perms = ref [];
Expand Down Expand Up @@ -333,16 +356,39 @@ and dec_field' ctxt d =
ctxt, None, None, fun ctxt ->
let adt_param tb = id tb.it.M.var in
let adt_con con = begin
{ con_name = !!! Source.no_region con.T.lab;
let con_name = !!! Source.no_region con.T.lab in
let mk_field_name i = !!! Source.no_region (Format.asprintf "%s$%i" con.T.lab i) in
{ con_name;
con_fields = match con.T.typ with
| T.Tup ts -> List.map (tr_typ ctxt) ts
| t -> [tr_typ ctxt t]
| T.Tup ts -> List.mapi (fun i t -> mk_field_name i, tr_typ ctxt t) ts
| t -> [mk_field_name 0, tr_typ ctxt t]
}
end in
AdtI ({ typ_id with note = NoInfo },
List.map adt_param typ_binds,
List.map adt_con cons),
NoInfo
| M.(TypD (typ_id, typ_binds, { note = T.Obj (T.Object, flds) as typ; _ })) ->
let con_name = mk_record_ctor typ_id in
{ ctxt with type_to_record_ctor = Type_map.add typ con_name ctxt.type_to_record_ctor },
None,
None,
fun ctxt ->
let adt_param tb = id tb.it.M.var in
let adt_field field =
let field_name = !!! Source.no_region (mk_record_field ~record_name:typ_id.it ~fld_name:field.T.lab) in
let field_typ = tr_typ ctxt field.T.typ in
field_name, field_typ
in
let adt_con =
{ con_name;
con_fields = List.map adt_field flds
}
in
AdtI ({ typ_id with note = NoInfo },
List.map adt_param typ_binds,
[ adt_con ]),
NoInfo
(* async functions *)
| M.(LetD ({it=VarP f;note;_},
{it=FuncE(x, sp, tp, p, t_opt, sugar,
Expand Down Expand Up @@ -504,7 +550,7 @@ and compile_while
let (!!) p = !!! at p in
let (invs, body) = extract_loop_invariants body in
let invs = (* TODO: automatic adding invariant into loop require more pondering*)
(* [!!(AndE(!!(CallE("$Perm", [self ctxt at])), *)
(* [!!(AndE(!!(CallE("$Perm", [self ctxt at])), *)
(* !!(CallE("$Inv", [self ctxt at]))))] *)
[!!(CallE("$Perm", [self ctxt at]))]
@ local_access_preds ctxt
Expand Down Expand Up @@ -720,6 +766,22 @@ and pat_match ctxt (scrut : M.exp) (p : M.pat) : exp list * (id * T.typ) list *
end in
let stmts = List.mapi (fun i (x, t) -> fld_assign_stmt i (LValueUninitVar x)) p_scope in
[cond], p_scope, (ds, stmts)
| M.ObjP pat_fields ->
let e_scrut = exp ctxt scrut in
let p_scope = List.map (fun M.{ it = { id = _; pat }; _ } -> unwrap_var_pat pat) pat_fields in
let ds = List.map (fun (x, t) -> !!(x, tr_typ ctxt t)) p_scope in
let fld_assign_stmt lval fld_name =
let rhs = !!(FldAcc (e_scrut, id fld_name)) in
!!(assign_stmt lval rhs)
in
let stmts =
List.map2
(fun M.{ it = { id = fld_name; pat = _ }; _ } (x, _) ->
let fld_name = get_record_field ctxt (T.normalize p.note) fld_name in
fld_assign_stmt (LValueUninitVar x) fld_name)
pat_fields p_scope
in
[], p_scope, (ds, stmts)
| M.WildP -> [], [], ([], [])
| _ -> unsupported p.at (Arrange.pat p)

Expand Down Expand Up @@ -874,6 +936,27 @@ and exp ctxt e =
!!(match e.it with
| M.TupE es -> CallE (tag.it, List.map (exp ctxt) es)
| _ -> CallE (tag.it, [exp ctxt e]))
| M.ObjE ([], flds) ->
(match T.normalize e.note.M.note_typ with
| T.Obj (T.Object, typ_flds) as t ->
let record_ctor_name = Type_map.find t ctxt.type_to_record_ctor in
let flds = List.map (fun M.{ it = { mut; id; exp }; _} ->
match mut.it with
| M.Const -> id.it, exp
| M.Var -> unsupported mut.at (Arrange.exp e)) flds
in
let args = List.map (fun T.{ lab; _ } ->
let rhs_exp = List.assoc lab flds in
exp ctxt rhs_exp) typ_flds
in
!!(CallE (record_ctor_name.it, args))
| T.Obj _ -> unsupported e.at (Arrange.exp e)
| _ -> assert false)
| M.DotE (proj, fld) when Type_map.mem (T.normalize proj.note.M.note_typ) ctxt.type_to_record_ctor ->
let proj_t = T.normalize proj.note.M.note_typ in
let proj = exp ctxt proj in
let fld = id (get_record_field ctxt proj_t fld) in
!!(FldAcc (proj, fld))
| M.TupE es ->
let n = List.length es in
ctxt.reqs.tuple_arities := IntSet.add n !(ctxt.reqs.tuple_arities);
Expand Down Expand Up @@ -949,7 +1032,8 @@ and tr_typ ctxt typ =
at = Source.no_region;
note = NoInfo }
and tr_typ' ctxt typ =
match typ, T.normalize typ with
let norm_typ = T.normalize typ in
match typ, norm_typ with
| _, T.Prim T.Int -> IntT
| _, T.Prim T.Nat -> IntT (* Viper has no native support for Nat, so translate to Int *)
| _, T.Prim T.Text -> IntT (* Viper has no native support for Text, so translate to uninterpreted Int values *)
Expand All @@ -959,6 +1043,9 @@ and tr_typ' ctxt typ =
| _, T.Tup ts ->
ctxt.reqs.tuple_arities := IntSet.add (List.length ts) !(ctxt.reqs.tuple_arities);
TupleT (List.map (tr_typ ctxt) ts)
| _, T.Obj (T.Object, flds) ->
let record_name = get_record_name ctxt norm_typ in
ConT (!!! Source.no_region record_name, [])
| T.Con (con, ts), _ -> ConT (!!! Source.no_region (Mo_types.Cons.name con), List.map (tr_typ ctxt) ts)
| _, t -> unsupported Source.no_region (Mo_types.Arrange_type.typ t)

Expand Down
1 change: 1 addition & 0 deletions test/viper/ok/record.silicon.ok
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Parse warning: In macro $Inv, the following parameters were defined but not used: $Self ([email protected])
10 changes: 10 additions & 0 deletions test/viper/ok/record.tc.ok
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
record.mo:7.7-7.11: warning [M0194], unused identifier fld1 (delete or rename to wildcard `_` or `_fld1`)
record.mo:8.7-8.11: warning [M0194], unused identifier fld2 (delete or rename to wildcard `_` or `_fld2`)
record.mo:12.7-12.9: warning [M0194], unused identifier aa (delete or rename to wildcard `_` or `_aa`)
record.mo:14.8-14.20: warning [M0194], unused identifier array_record (delete or rename to wildcard `_` or `_array_record`)
record.mo:22.8-22.20: warning [M0194], unused identifier tuple_record (delete or rename to wildcard `_` or `_tuple_record`)
record.mo:33.8-33.19: warning [M0194], unused identifier call_record (delete or rename to wildcard `_` or `_call_record`)
record.mo:44.8-44.19: warning [M0194], unused identifier pass_record (delete or rename to wildcard `_` or `_pass_record`)
record.mo:49.8-49.23: warning [M0194], unused identifier match_on_record (delete or rename to wildcard `_` or `_match_on_record`)
record.mo:55.13-55.14: warning [M0194], unused identifier c (delete or rename to wildcard `_` or `_c`)
record.mo:61.8-61.35: warning [M0194], unused identifier record_types_are_structural (delete or rename to wildcard `_` or `_record_types_are_structural`)
147 changes: 147 additions & 0 deletions test/viper/ok/record.vpr.ok
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/* BEGIN PRELUDE */
/* Array encoding */
domain Array {
function $loc(a: Array, i: Int): Ref
function $size(a: Array): Int
function $loc_inv1(r: Ref): Array
function $loc_inv2(r: Ref): Int
axiom $all_diff_array { forall a: Array, i: Int :: {$loc(a, i)} $loc_inv1($loc(a, i)) == a && $loc_inv2($loc(a, i)) == i }
axiom $size_nonneg { forall a: Array :: $size(a) >= 0 }
}
define $array_acc(a, t, p) forall j: Int :: 0 <= j && j < $size(a) ==> acc($loc(a, j).t, p)
define $array_untouched(a, t) forall j: Int :: 0 <= j && j < $size(a) ==> $loc(a, j).t == old($loc(a, j).t)
define $array_init(a, t, x) forall i : Int :: {$loc(a, i).t} 0 <= i && i < $size(a) ==> $loc(a, i).t == x
/* Tuple encoding */
adt Tuple$2 [T0, T1] { Tup$2(tup$2$0 : T0, tup$2$1 : T1) }
/* Option encoding */
adt Option[T] {
None()
Some(some$0: T)
}
/* Text encoding */
function $concat(a: Int, b: Int): Int
/* Typed references */
field $c_R1: R1
/* END PRELUDE */

define $Perm($Self) (((((true && acc(($Self).fld1,write)) && acc(($Self).fld2,write)) &&
acc(($Self).empty,write)) && acc(($Self).aa,write)))
define $Inv($Self) (true)
method __init__($Self: Ref)

requires $Perm($Self)
ensures $Perm($Self)
ensures $Inv($Self)
{
($Self).fld1 := $RecordCtor_R1(42, 3);
($Self).fld2 := $RecordCtor_R1(42, 3);
($Self).empty := $RecordCtor_R1(0, 4);
($Self).aa := 42;
}
adt R { $RecordCtor_R($R$aa : Int, $R$b : Int) }
adt R1 { $RecordCtor_R1($R1$aa : Int, $R1$b : Int) }
field fld1: R1
field fld2: R1
field empty: R1
field aa: Int
method array_record($Self: Ref)

requires $Perm($Self)
ensures $Perm($Self)
{ var arr: Array
inhale $array_acc(arr, $c_R1, write);
inhale ($size(arr) == 2);
($loc(arr, 0)).$c_R1 := ($Self).empty;
($loc(arr, 1)).$c_R1 := ($Self).empty;
($loc(arr, 0)).$c_R1 := $RecordCtor_R1(100, 0);
($loc(arr, 1)).$c_R1 := $RecordCtor_R1(200, 1);
assert ((($loc(arr, 0)).$c_R1 == $RecordCtor_R1(100, 0)) && ((
$loc(arr,
1)).$c_R1 ==
$RecordCtor_R1(200, 1)));
label $Ret;
}
method tuple_record($Self: Ref)

requires $Perm($Self)
ensures $Perm($Self)
{ var tup: Tuple$2[R1, R1]
tup := Tup$2($RecordCtor_R1(100, 0), $RecordCtor_R1(200, 1));
assert (((tup).tup$2$0 == $RecordCtor_R1(100, 0)) && ((tup).tup$2$1 ==
$RecordCtor_R1(200, 1)));
label $Ret;
}
method get_record($Self: Ref)
returns ($Res: R1)
requires $Perm($Self)
ensures $Perm($Self)
ensures ($Res == $RecordCtor_R1(100, 0))
{
$Res := $RecordCtor_R1(100, 0);
goto $Ret;
label $Ret;
}
method call_record($Self: Ref)

requires $Perm($Self)
ensures $Perm($Self)
{ var res: R1
res := get_record($Self);
assert ((res).$R1$b == 0);
label $Ret;
}
method record_arg($Self: Ref, r: R1)
returns ($Res: R1)
requires $Perm($Self)
requires ((r).$R1$aa == 100)
ensures $Perm($Self)
ensures (($Res).$R1$aa == 100)
{
$Res := $RecordCtor_R1(100, 0);
goto $Ret;
label $Ret;
}
method pass_record($Self: Ref)

requires $Perm($Self)
ensures $Perm($Self)
{ var res: R1
res := record_arg($Self, $RecordCtor_R1(100, 2));
assert ((res).$R1$aa == 100);
label $Ret;
}
method match_on_record($Self: Ref)
returns ($Res: Int)
requires $Perm($Self)
ensures $Perm($Self)
ensures ($Res == 42)
{ var r: R1
r := get_record($Self);
if (true)
{ var number: Int
var b: Int
var c: Int
number := (r).$R1$aa;
b := (r).$R1$b;
c := b;
$Res := (number - 58);
goto $Ret;
};
label $Ret;
}
method record_types_are_structural($Self: Ref)

requires $Perm($Self)
ensures $Perm($Self)
{ var r: R1
var r1: R1
r := $RecordCtor_R1(100, 0);
r1 := $RecordCtor_R1(100, 0);
assert (r == r1);
label $Ret;
}
adt RecursiveRecord1
{ $RecordCtor_RecursiveRecord1($RecursiveRecord1$x : Option[RecursiveRecord1],
$RecursiveRecord1$y : Option[RecursiveRecord2]) }
adt RecursiveRecord2
{ $RecordCtor_RecursiveRecord2($RecursiveRecord2$x : Option[RecursiveRecord1]) }
10 changes: 10 additions & 0 deletions test/viper/ok/record.vpr.stderr.ok
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
record.mo:7.7-7.11: warning [M0194], unused identifier fld1 (delete or rename to wildcard `_` or `_fld1`)
record.mo:8.7-8.11: warning [M0194], unused identifier fld2 (delete or rename to wildcard `_` or `_fld2`)
record.mo:12.7-12.9: warning [M0194], unused identifier aa (delete or rename to wildcard `_` or `_aa`)
record.mo:14.8-14.20: warning [M0194], unused identifier array_record (delete or rename to wildcard `_` or `_array_record`)
record.mo:22.8-22.20: warning [M0194], unused identifier tuple_record (delete or rename to wildcard `_` or `_tuple_record`)
record.mo:33.8-33.19: warning [M0194], unused identifier call_record (delete or rename to wildcard `_` or `_call_record`)
record.mo:44.8-44.19: warning [M0194], unused identifier pass_record (delete or rename to wildcard `_` or `_pass_record`)
record.mo:49.8-49.23: warning [M0194], unused identifier match_on_record (delete or rename to wildcard `_` or `_match_on_record`)
record.mo:55.13-55.14: warning [M0194], unused identifier c (delete or rename to wildcard `_` or `_c`)
record.mo:61.8-61.35: warning [M0194], unused identifier record_types_are_structural (delete or rename to wildcard `_` or `_record_types_are_structural`)
Loading

0 comments on commit ae07bc1

Please sign in to comment.