-
Loïc Correnson authoredLoïc Correnson authored
MemoryContext.ml 17.89 KiB
(**************************************************************************)
(* *)
(* This file is part of WP plug-in of Frama-C. *)
(* *)
(* Copyright (C) 2007-2021 *)
(* CEA (Commissariat a l'energie atomique et aux energies *)
(* alternatives) *)
(* *)
(* you can redistribute it and/or modify it under the terms of the GNU *)
(* Lesser General Public License as published by the Free Software *)
(* Foundation, version 2.1. *)
(* *)
(* It is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU Lesser General Public License for more details. *)
(* *)
(* See the GNU Lesser General Public License version 2.1 *)
(* for more details (enclosed in the file licenses/LGPLv2.1). *)
(* *)
(**************************************************************************)
(* -------------------------------------------------------------------------- *)
(* --- Variable Partitionning --- *)
(* -------------------------------------------------------------------------- *)
type validity = Valid | Nullable
type param =
| NotUsed | ByAddr | ByValue | ByShift | ByRef
| InContext of validity | InArray of validity
let pp_nullable fmt = function
| Valid -> ()
| Nullable -> Format.pp_print_string fmt " (nullable)"
let pp_param fmt = function
| NotUsed -> Format.pp_print_string fmt "not used"
| ByAddr -> Format.pp_print_string fmt "in heap"
| ByValue -> Format.pp_print_string fmt "by value"
| ByShift -> Format.pp_print_string fmt "by value with shift"
| ByRef -> Format.pp_print_string fmt "by ref"
| InContext n -> Format.fprintf fmt "in context%a" pp_nullable n
| InArray n -> Format.fprintf fmt "in array%a" pp_nullable n
(* -------------------------------------------------------------------------- *)
(* --- Separation Hypotheses --- *)
(* -------------------------------------------------------------------------- *)
open Cil_types
type zone =
| Var of varinfo (* &x - the cell x *)
| Ptr of varinfo (* p - the cell pointed by p *)
| Arr of varinfo (* p+(..) - the cell and its neighbors pointed by p *)
type partition = {
globals : zone list ; (* [ &G , G[...], ... ] *)
to_heap : zone list ; (* [ p, ... ] *)
context : zone list ; (* [ p+(..), ... ] *)
nullable : zone list ; (* [ p+(..), ... ] but can be NULL *)
by_addr : zone list ; (* [ &(x + ..), ... ] *)
}
(* -------------------------------------------------------------------------- *)
(* --- Partition --- *)
(* -------------------------------------------------------------------------- *)
let empty = {
globals = [] ;
context = [] ;
nullable = [] ;
to_heap = [] ;
by_addr = [] ;
}
let set x p w =
match p with
| NotUsed -> w
| ByAddr -> { w with by_addr = Var x :: w.by_addr }
| ByRef ->
if Cil.isFunctionType x.vtype then w else
{ w with context = Ptr x :: w.context }
| InContext v ->
if Cil.isFunctionType x.vtype then w else
begin match v with
| Nullable -> { w with nullable = Ptr x :: w.nullable }
| Valid -> { w with context = Ptr x :: w.context }
end
| InArray v ->
if Cil.isFunctionType x.vtype then w else
begin match v with
| Nullable -> { w with nullable = Arr x :: w.nullable }
| Valid -> { w with context = Arr x :: w.context }
end
| ByValue | ByShift ->
if x.vghost then w else
if Cil.isFunctionType x.vtype then w else
if x.vglob && (x.vstorage <> Static || x.vaddrof) then
let z = if Cil.isArrayType x.vtype then Arr x else Var x in
{ w with globals = z :: w.globals }
else
if x.vformal && Cil.isPointerType x.vtype then
let z = if p = ByShift then Arr x else Ptr x in
{ w with to_heap = z :: w.to_heap }
else w
(* -------------------------------------------------------------------------- *)
(* --- Building Annotations --- *)
(* -------------------------------------------------------------------------- *)
open Logic_const
let rec ptr_of = function
| Ctype t -> Ctype (TPtr(t, []))
| t when Logic_typing.is_set_type t ->
let t = Logic_typing.type_of_set_elem t in
Logic_const.make_set_type (ptr_of t)
| _ -> assert false
let rec addr_of_lval ?loc term =
let typ = ptr_of term.term_type in
match term.term_node with
| TLval lv ->
Logic_utils.mk_logic_AddrOf ?loc lv typ
| TCastE (_, t) | TLogic_coerce (_, t) ->
addr_of_lval ?loc t
| Tif(c, t, e) ->
let t = addr_of_lval ?loc t in
let e = addr_of_lval ?loc e in
Logic_const.term ?loc (Tif(c, t, e)) typ
| Tat( _, _) ->
term
| Tunion l ->
let l = List.map (addr_of_lval ?loc) l in
Logic_const.term ?loc (Tunion l) typ
| Tinter l ->
let l = List.map (addr_of_lval ?loc) l in
Logic_const.term ?loc (Tinter l) typ
| Tcomprehension (t, qs, p) ->
let t = addr_of_lval ?loc t in
Logic_const.term ?loc (Tcomprehension (t,qs,p)) typ
| _ -> term
let type_of_zone = function
| Ptr vi -> vi.vtype
| Var vi -> TPtr(vi.vtype, [])
| Arr vi when Cil.isPointerType vi.vtype -> vi.vtype
| Arr vi -> TPtr(Cil.typeOf_array_elem vi.vtype, [])
let zone_to_term ?(to_char=false) loc zone =
let typ = Ctype (type_of_zone zone) in
let lval vi = TVar (Cil.cvar_to_lvar vi), TNoOffset in
let loc_range ptr =
if not to_char then ptr
else
let pointed =
match typ with
| (Ctype (TPtr (t, []))) -> t
| _ -> assert false (* typ has been generated by type_of_zone *)
in
let len = Logic_utils.expr_to_term (Cil.sizeOf ~loc pointed) in
let last = term (TBinOp(MinusA, len, tinteger ~loc 1)) len.term_type in
let range = trange ~loc (Some (tinteger ~loc 0), Some last) in
let ptr = Logic_utils.mk_cast ~loc Cil.charPtrType ptr in
term ~loc (TBinOp(PlusPI, ptr, range)) ptr.term_type
in
match zone with
| Var vi -> loc_range (term ~loc (TAddrOf(lval vi)) typ)
| Ptr vi -> loc_range (term ~loc (TLval(lval vi)) typ)
| Arr vi ->
let ptr =
if Cil.isArrayType vi.vtype
then term ~loc (TStartOf (lval vi)) typ
else term ~loc (TLval(lval vi)) typ
in
let ptr =
if not to_char then ptr
else Logic_utils.mk_cast ~loc Cil.charPtrType ptr
in
let range = trange ~loc (None, None) in
term ~loc (TBinOp(PlusPI, ptr, range)) ptr.term_type
let region_to_term loc = function
| [] -> term ~loc Tempty_set (Ctype Cil.charPtrType)
| [z] -> zone_to_term loc z
| x :: tl as l ->
let fst = type_of_zone x in
let tl = List.map type_of_zone tl in
let to_char = not (List.for_all (Cil_datatype.Typ.equal fst) tl) in
let set_typ =
make_set_type (Ctype (if to_char then Cil.charPtrType else fst))
in
term ~loc (Tunion (List.map (zone_to_term ~to_char loc) l)) set_typ
let separated_list ?loc = function
| [] | [ _ ] -> ptrue
| l ->
let comp = Cil_datatype.Term.compare in
pseparated ?loc (List.sort comp l)
let term_separated_from_regions loc assigned l =
separated_list ~loc (assigned :: List.map (region_to_term loc) l)
let valid_region loc r =
let t = region_to_term loc r in
pvalid ~loc (here_label, t)
let valid_or_null_region loc r =
let t = region_to_term loc r in
let null = term ~loc Tnull t.term_type in
por (valid_region loc r, prel ~loc (Req, t, null))
let rec rank p = match p.pred_content with
| Pvalid _ -> 1
| Pseparated _ -> 2
| Pimplies(_,p) -> rank p
| Por(p,q) | Pand(p,q) -> max (rank p) (rank q)
| _ -> 0
let compare p q =
let r = rank p - rank q in
if r <> 0 then r else Logic_utils.compare_predicate p q
let normalize ps =
List.sort_uniq compare @@
List.filter (fun p -> not(Logic_utils.is_trivially_true p)) ps
let ptrset { term_type = t } =
let open Logic_typing in
is_pointer_type t || (is_set_type t && is_pointer_type (type_of_element t))
(* -------------------------------------------------------------------------- *)
(* --- Partition Helpers --- *)
(* -------------------------------------------------------------------------- *)
let welltyped zones =
let rec partition_by_type t acc l =
match l, acc with
| [], _ ->
acc
| x :: l, [] ->
partition_by_type (type_of_zone x) [[x]] l
| x :: l, p :: acc' when Cil_datatype.Typ.equal t (type_of_zone x) ->
partition_by_type t ((x :: p) :: acc') l
| x :: l, acc ->
partition_by_type (type_of_zone x) ([x] :: acc) l
in
let compare_zone a b =
Cil_datatype.Typ.compare (type_of_zone a) (type_of_zone b) in
partition_by_type Cil.voidType [] (List.sort compare_zone zones)
let global_zones partition =
List.map (fun z -> [z]) partition.globals
let context_zones partition =
List.map (fun z -> [z]) partition.context
let nullable_zones partition =
List.map (fun z -> [z]) partition.nullable
let heaps partition = welltyped partition.to_heap
let addr_of_vars partition = welltyped partition.by_addr
(* -------------------------------------------------------------------------- *)
(* --- Computing Separation --- *)
(* -------------------------------------------------------------------------- *)
(* Memory regions shall be separated with each others *)
let main_separation loc globals context nullable heaps =
if context = [] && nullable = [] && heaps = [] then []
else
let l_zones = match heaps with
| [] -> [globals @ context]
| heaps -> List.map (fun h -> h :: (globals @ context)) heaps
in
let regions_to_terms = List.map (region_to_term loc) in
let guard_nullable tn sep =
pimplies ~loc (prel ~loc (Rneq, tn, term ~loc Tnull tn.term_type), sep)
in
let acc_with_nullable tn zones =
List.cons @@
guard_nullable tn (separated_list ~loc (tn :: regions_to_terms zones))
in
let no_nullable zones = separated_list ~loc @@ regions_to_terms zones in
let nullable_inter nullable =
let separated_nullable (p, q) =
guard_nullable p @@ guard_nullable q @@ pseparated ~loc [ p ; q ]
in
let rec collect_pairs = function
(* trivial cases *)
| [] -> [] | [_] -> []
| p :: l ->
let acc_sep q = List.cons @@ separated_nullable (p, q) in
List.fold_right acc_sep l @@ collect_pairs l
in
collect_pairs nullable
in
match nullable with
| [] -> List.map no_nullable l_zones
| nullable ->
let t_nullable = regions_to_terms nullable in
let sep_nullable = nullable_inter t_nullable in
let fold n = List.fold_right (acc_with_nullable n) l_zones in
List.fold_right fold t_nullable sep_nullable
(* Filter assigns *)
let assigned_locations kf filter =
let add_from l (e, _ds) =
if filter e.it_content then e :: l else l
in
let add_assign _emitter assigns l = match assigns with
| WritesAny -> l
| Writes froms -> List.fold_left add_from l froms
in
Annotations.fold_assigns add_assign kf Cil.default_behavior_name []
(* Locations assigned by pointer from a call *)
let assigned_via_pointers kf =
let rec assigned_via_pointer t =
match t.term_node with
| TLval (TMem _, _) ->
true
| TCastE (_, t) | TLogic_coerce (_, t)
| Tcomprehension(t, _, _) | Tat (t, _) ->
assigned_via_pointer t
| Tunion l | Tinter l ->
List.exists assigned_via_pointer l
| Tif (_, t1, t2) ->
assigned_via_pointer t1 || assigned_via_pointer t2
| _ ->
false
in
assigned_locations kf assigned_via_pointer
(* Checks whether a term refers to Post *)
let post_term t =
let exception Post_value in
let v = object
inherit Cil.nopCilVisitor
method! vlogic_label = function
| BuiltinLabel Post -> raise Post_value
| _ -> Cil.SkipChildren
method! vterm_lval = function
| TResult _, _ -> raise Post_value
| _ -> Cil.DoChildren
end in
try ignore (Cil.visitCilTerm v t) ; false
with Post_value -> true
(* Computes conditions from call assigns *)
let assigned_separation kf loc globals =
let addr_of t = addr_of_lval ~loc t.it_content in
let asgnd_ptrs = List.map addr_of (assigned_via_pointers kf) in
let folder (req, ens) t =
let sep = term_separated_from_regions loc t globals in
if post_term t then (req, sep :: ens) else (sep :: req, ens)
in
List.fold_left folder ([],[]) asgnd_ptrs
(* Computes conditions from partition *)
let clauses_of_partition kf loc p =
let globals = global_zones p in
let main_sep =
main_separation loc globals (context_zones p) (nullable_zones p) (heaps p)
in
let assigns_sep_req, assigns_sep_ens = assigned_separation kf loc globals in
let context_valid = List.map (valid_region loc) (context_zones p) in
let nullable_valid = List.map (valid_or_null_region loc) (nullable_zones p) in
let reqs = main_sep @ assigns_sep_req @ context_valid @ nullable_valid in
let reqs = normalize reqs in
let ens = normalize assigns_sep_ens in
reqs, ens
(* Computes conditions from return *)
let out_pointers_separation kf loc p =
let ret_t = Kernel_function.get_return_type kf in
let addr_of t = addr_of_lval ~loc t.it_content in
let asgnd_ptrs =
Extlib.filter_map
(* Search assigned pointers via a pointer,
e.g. 'assigns *p ;' with '*p' of type pointer or set of pointers *)
(fun t -> ptrset t.it_content) addr_of (assigned_via_pointers kf)
in
let asgnd_ptrs =
if Cil.isPointerType ret_t then tresult ~loc ret_t :: asgnd_ptrs
else asgnd_ptrs
in
let formals_separation =
let formal_zone = function Var v -> v.vformal | _ -> false in
let formal_partition =
{ p with by_addr = List.filter formal_zone p.by_addr }
in
let formals = addr_of_vars formal_partition in
List.map (fun t -> term_separated_from_regions loc t formals) asgnd_ptrs
in
let globals_separation =
let globals = global_zones p in
List.map (fun t -> term_separated_from_regions loc t globals) asgnd_ptrs
in
normalize (formals_separation @ globals_separation)
(* Computes all conditions from behavior *)
let compute_behavior kf name hypotheses_computer =
let partition = hypotheses_computer kf in
let loc = Kernel_function.get_location kf in
let reqs, ens = clauses_of_partition kf loc partition in
let ens = out_pointers_separation kf loc partition @ ens in
let reqs = List.map new_predicate reqs in
let ens = List.map (fun p -> Normal, new_predicate p) ens in
match reqs, ens with
| [], [] -> None
| reqs, ens ->
Some {
b_name = Annotations.fresh_behavior_name kf ("wp_" ^ name) ;
b_requires = reqs ;
b_assumes = [] ;
b_post_cond = ens ;
b_assigns = WritesAny ;
b_allocation = FreeAllocAny ;
b_extended = []
}
(* -------------------------------------------------------------------------- *)
(* --- Memoization --- *)
(* -------------------------------------------------------------------------- *)
module Table =
State_builder.Hashtbl
(Cil_datatype.Kf.Hashtbl)
(Datatype.Option(Cil_datatype.Funbehavior))
(struct
let name = "MemoryContext.Table"
let size = 17
let dependencies = [ Ast.self ]
end)
module RegisteredHypotheses =
State_builder.Set_ref
(Cil_datatype.Kf.Set)
(struct
let name = "Wp.MemoryContext.RegisteredHypotheses"
let dependencies = [Ast.self]
end)
let compute name hypotheses_computer =
Globals.Functions.iter
(fun kf -> ignore (compute_behavior kf name hypotheses_computer))
let get_behavior kf name hypotheses_computer =
Table.memo
begin fun kf ->
AssignsCompleteness.warn kf ;
compute_behavior kf name hypotheses_computer
end
kf
(* -------------------------------------------------------------------------- *)
(* --- External API --- *)
(* -------------------------------------------------------------------------- *)
let print_memory_context kf bhv fmt =
begin
let printer = new Printer.extensible_printer () in
let pp_vdecl = printer#without_annot printer#vdecl in
Format.fprintf fmt "@[<hv 0>@[<hv 3>/*@@@ %a" Cil_printer.pp_behavior bhv ;
let vkf = Kernel_function.get_vi kf in
Format.fprintf fmt "@ @]*/@]@\n@[<hov 2>%a;@]@\n"
pp_vdecl vkf ;
end
let warn kf name hyp_computer =
match get_behavior kf name hyp_computer with
| None -> ()
| Some bhv ->
Wp_parameters.warning
~current:false ~once:true ~source:(fst(Kernel_function.get_location kf))
"@[<hv 0>Memory model hypotheses for function '%s':@ %t@]"
(Kernel_function.get_name kf)
(print_memory_context kf bhv)
let emitter =
Emitter.(create "Wp.Hypotheses" [Funspec] ~correctness:[] ~tuning:[])
let add_behavior kf name hypotheses_computer =
if RegisteredHypotheses.mem kf then ()
else begin
begin match get_behavior kf name hypotheses_computer with
| None -> ()
| Some bhv -> Annotations.add_behaviors emitter kf [bhv]
end ;
RegisteredHypotheses.add kf
end
(* -------------------------------------------------------------------------- *)