Skip to content
Snippets Groups Projects
MemVar.ml 56.92 KiB
(**************************************************************************)
(*                                                                        *)
(*  This file is part of WP plug-in of Frama-C.                           *)
(*                                                                        *)
(*  Copyright (C) 2007-2022                                               *)
(*    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).            *)
(*                                                                        *)
(**************************************************************************)

(* -------------------------------------------------------------------------- *)
(* --- No-Aliasing Memory Model                                           --- *)
(* -------------------------------------------------------------------------- *)

open Cil_types
open Cil_datatype
open Ctypes

open MemoryContext
open Lang
open Lang.F
open Sigs

module type VarUsage =
sig
  val datatype : string
  val param : varinfo -> MemoryContext.param
  val iter: ?kf:kernel_function -> init:bool -> (varinfo -> unit) -> unit
end

module Raw : VarUsage =
struct
  let datatype = "Raw"
  let param _x = MemoryContext.ByValue
  let iter ?kf ~init f =
    begin
      ignore init ;
      Globals.Vars.iter (fun x _initinfo -> f x) ;
      match kf with
      | None -> ()
      | Some kf -> List.iter f (Kernel_function.get_formals kf) ;
    end
end

module Static : VarUsage =
struct
  let datatype = "Static"
  let param x =
    let open Cil_types in
    if x.vaddrof || Cil.isArrayType x.vtype || Cil.isPointerType x.vtype
    then MemoryContext.ByAddr else MemoryContext.ByValue
  let iter = Raw.iter
end

module Make(V : VarUsage)(M : Sigs.Model) =
struct

  (* -------------------------------------------------------------------------- *)
  (* ---  Model                                                             --- *)
  (* -------------------------------------------------------------------------- *)

  let datatype = "MemVar." ^ V.datatype ^ M.datatype
  let configure = M.configure
  let no_binder = { bind = fun _ f v -> f v }
  let configure_ia _ = no_binder

  let hypotheses p =
    let kf,init = match WpContext.get_scope () with
      | WpContext.Global -> None,false
      | WpContext.Kf f ->
        Some f, Globals.is_entry_point ~when_lib_entry:false f in
    let w = ref p in
    V.iter ?kf ~init (fun vi -> w := MemoryContext.set vi (V.param vi) !w) ;
    M.hypotheses !w

  (* -------------------------------------------------------------------------- *)
  (* ---  Chunk                                                             --- *)
  (* -------------------------------------------------------------------------- *)

  type chunk =
    | Var of varinfo
    | Alloc of varinfo
    | Init of varinfo
    | Mem of M.Chunk.t

  let is_framed_var x = not x.vglob && not x.vaddrof
  (* Can not use VarUsage info, since (&x) can still be passed
     to the function and be modified by the call (when it assigns everything). *)

  module VAR =
  struct
    type t = varinfo
    let self = "var"
    let hash = Varinfo.hash
    let equal = Varinfo.equal
    let compare = Varinfo.compare
    let pretty = Varinfo.pretty
    let typ_of_chunk x =
      match V.param x with
      | ByRef -> Cil.typeOf_pointed x.vtype
      | _ -> x.vtype
    let tau_of_chunk x = Lang.tau_of_ctype (typ_of_chunk x)
    let is_framed = is_framed_var
    let basename_of_chunk = LogicUsage.basename
  end

  module VALLOC =
  struct
    type t = varinfo
    let self = "alloc"
    let hash = Varinfo.hash
    let compare = Varinfo.compare
    let equal = Varinfo.equal
    let pretty = Varinfo.pretty
    let tau_of_chunk _x = Qed.Logic.Bool
    let basename_of_chunk x =
      match V.param x with
      | ByRef ->
        "ra_" ^ LogicUsage.basename x
      | NotUsed | ByValue | ByShift | ByAddr | InContext _ | InArray _ ->
        "ta_" ^ LogicUsage.basename x
    let is_framed = is_framed_var
  end

  module VINIT =
  struct
    type t = varinfo
    let self = "init"
    let hash = Varinfo.hash
    let compare = Varinfo.compare
    let equal = Varinfo.equal
    let pretty = Varinfo.pretty
    let typ_of_chunk x =
      match V.param x with
      | ByRef -> Cil.typeOf_pointed x.vtype
      | _ -> x.vtype
    let tau_of_chunk x = Lang.init_of_ctype (typ_of_chunk x)
    let is_framed = is_framed_var
    let basename_of_chunk x = "Init_" ^ (LogicUsage.basename x)
  end

  module Chunk =
  struct
    type t = chunk
    let self = "varmem"
    let hash = function
      | Var x -> 3 * Varinfo.hash x
      | Alloc x -> 5 * Varinfo.hash x
      | Mem m -> 7 * M.Chunk.hash m
      | Init x -> 11 * Varinfo.hash x
    let compare c1 c2 =
      if c1 == c2 then 0 else
        match c1 , c2 with
        | Var x , Var y
        | Alloc x , Alloc y
        | Init x, Init y -> Varinfo.compare x y
        | Mem p , Mem q -> M.Chunk.compare p q
        | Var _ , _ -> (-1)
        | _ , Var _ -> 1
        | Init _, _ -> (-1)
        | _, Init _ -> 1
        | Alloc _  , _ -> (-1)
        | _ , Alloc _ -> 1
    let equal c1 c2 = (compare c1 c2 = 0)
    let pretty fmt = function
      | Var x -> Varinfo.pretty fmt x
      | Alloc x -> Format.fprintf fmt "alloc(%a)" Varinfo.pretty x
      | Init x -> Format.fprintf fmt "init(%a)" Varinfo.pretty x
      | Mem m -> M.Chunk.pretty fmt m
    let tau_of_chunk = function
      | Var x -> VAR.tau_of_chunk x
      | Alloc x -> VALLOC.tau_of_chunk x
      | Init x -> VINIT.tau_of_chunk x
      | Mem m -> M.Chunk.tau_of_chunk m
    let basename_of_chunk = function
      | Var x -> VAR.basename_of_chunk x
      | Alloc x -> VALLOC.basename_of_chunk x
      | Init x -> VINIT.basename_of_chunk x
      | Mem m -> M.Chunk.basename_of_chunk m
    let is_framed = function
      | Var x -> VAR.is_framed x
      | Alloc x -> VALLOC.is_framed x
      | Init x -> VINIT.is_framed x
      | Mem m -> M.Chunk.is_framed m
  end

  (* -------------------------------------------------------------------------- *)
  (* ---  Sigma                                                             --- *)
  (* -------------------------------------------------------------------------- *)

  module HEAP = Qed.Collection.Make(VAR)
  module TALLOC = Qed.Collection.Make(VALLOC)
  module TINIT = Qed.Collection.Make(VINIT)
  module SIGMA = Sigma.Make(VAR)(HEAP)
  module ALLOC = Sigma.Make(VALLOC)(TALLOC)
  module INIT = Sigma.Make(VINIT)(TINIT)
  module Heap = Qed.Collection.Make(Chunk)
  type sigma = {
    mem : M.Sigma.t ;
    vars : SIGMA.t ;
    init : INIT.t ;
    alloc : ALLOC.t ;
  }

  module Sigma =
  struct
    type t = sigma
    type chunk = Chunk.t
    module Chunk = Heap
    type domain = Heap.set
    let empty = Heap.Set.empty
    let union = Heap.Set.union

    let create () = {
      vars = SIGMA.create () ;
      init = INIT.create () ;
      alloc = ALLOC.create () ;
      mem = M.Sigma.create () ;
    }

    let copy s = {
      vars = SIGMA.copy s.vars ;
      init = INIT.copy s.init ;
      alloc = ALLOC.copy s.alloc ;
      mem = M.Sigma.copy s.mem ;
    }

    let choose s1 s2 =
      let s = SIGMA.choose s1.vars s2.vars in
      let i = INIT.choose s1.init s2.init in
      let a = ALLOC.choose s1.alloc s2.alloc in
      let m = M.Sigma.choose s1.mem s2.mem in
      { vars = s ; alloc = a ; mem = m ; init = i }

    let merge s1 s2 =
      let s,pa1,pa2 = SIGMA.merge s1.vars s2.vars in
      let i,ia1,ia2 = INIT.merge s1.init s2.init in
      let a,ta1,ta2 = ALLOC.merge s1.alloc s2.alloc in
      let m,qa1,qa2 = M.Sigma.merge s1.mem s2.mem in
      { vars = s ; alloc = a ; mem = m ; init = i } ,
      Passive.union (Passive.union (Passive.union pa1 ta1) qa1) ia1 ,
      Passive.union (Passive.union (Passive.union pa2 ta2) qa2) ia2

    let merge_list l =
      let s,pa = SIGMA.merge_list (List.map (fun s -> s.vars) l) in
      let i,ia = INIT.merge_list (List.map (fun s -> s.init) l) in
      let a,ta = ALLOC.merge_list (List.map (fun s -> s.alloc) l) in
      let m,qa = M.Sigma.merge_list (List.map (fun s -> s.mem) l) in
      { vars = s ; alloc = a ; mem = m ; init = i } ,
      let union = List.map2 Passive.union in
      union (union (union pa ta) qa) ia

    let join s1 s2 =
      Passive.union
        (Passive.union
           (Passive.union
              (SIGMA.join s1.vars s2.vars)
              (ALLOC.join s1.alloc s2.alloc))
           (M.Sigma.join s1.mem s2.mem))
        (INIT.join s1.init s2.init)

    let get s = function
      | Var x -> SIGMA.get s.vars x
      | Init x -> INIT.get s.init x
      | Alloc x -> ALLOC.get s.alloc x
      | Mem m -> M.Sigma.get s.mem m
    let mem s = function
      | Var x -> SIGMA.mem s.vars x
      | Init x -> INIT.mem s.init x
      | Alloc x -> ALLOC.mem s.alloc x
      | Mem m -> M.Sigma.mem s.mem m

    let value s c = e_var (get s c)

    let iter f s =
      begin
        SIGMA.iter (fun x -> f (Var x)) s.vars ;
        INIT.iter (fun x -> f (Init x)) s.init ;
        ALLOC.iter (fun x -> f (Alloc x)) s.alloc ;
        M.Sigma.iter (fun m -> f (Mem m)) s.mem ;
      end

    let iter2 f s t =
      begin
        SIGMA.iter2 (fun x a b -> f (Var x) a b) s.vars t.vars ;
        INIT.iter2 (fun x a b -> f (Init x) a b) s.init t.init ;
        ALLOC.iter2 (fun x a b -> f (Alloc x) a b) s.alloc t.alloc ;
        M.Sigma.iter2 (fun m p q -> f (Mem m) p q) s.mem t.mem ;
      end

    let domain_partition r =
      begin
        let xs = ref HEAP.Set.empty in
        let is = ref TINIT.Set.empty in
        let ts = ref TALLOC.Set.empty in
        let ms = ref M.Heap.Set.empty in
        Heap.Set.iter
          (function
            | Var x -> xs := HEAP.Set.add x !xs
            | Init x -> is := TINIT.Set.add x !is
            | Alloc x -> ts := TALLOC.Set.add x !ts
            | Mem c -> ms := M.Heap.Set.add c !ms
          ) r ;
        !xs , !is, !ts , !ms
      end

    let domain_var xs =
      HEAP.Set.fold (fun x s -> Heap.Set.add (Var x) s) xs Heap.Set.empty

    let domain_init xs =
      TINIT.Set.fold (fun x s -> Heap.Set.add (Init x) s) xs Heap.Set.empty

    let domain_alloc ts =
      TALLOC.Set.fold (fun x s -> Heap.Set.add (Alloc x) s) ts Heap.Set.empty

    let domain_mem ms =
      M.Heap.Set.fold (fun m s -> Heap.Set.add (Mem m) s) ms Heap.Set.empty

    let assigned ~pre ~post w =
      let w_vars , w_init, w_alloc , w_mem = domain_partition w in
      let h_vars = SIGMA.assigned ~pre:pre.vars ~post:post.vars w_vars in
      let h_init = INIT.assigned ~pre:pre.init ~post:post.init w_init in
      let h_alloc = ALLOC.assigned ~pre:pre.alloc ~post:post.alloc w_alloc in
      let h_mem = M.Sigma.assigned ~pre:pre.mem ~post:post.mem w_mem in
      Bag.ulist [h_vars;h_init;h_alloc;h_mem]

    let havoc s r =
      let rvar , rinit, ralloc , rmem = domain_partition r
      in {
        vars = SIGMA.havoc s.vars rvar ;
        init = INIT.havoc s.init rinit ;
        alloc = ALLOC.havoc s.alloc ralloc ;
        mem = M.Sigma.havoc s.mem rmem ;
      }

    let havoc_chunk s = function
      | Var x -> { s with vars = SIGMA.havoc_chunk s.vars x }
      | Init x -> { s with init = INIT.havoc_chunk s.init x }
      | Alloc x -> { s with alloc = ALLOC.havoc_chunk s.alloc x }
      | Mem m -> { s with mem = M.Sigma.havoc_chunk s.mem m }

    let havoc_any ~call s = {
      alloc = s.alloc ;
      vars = SIGMA.havoc_any ~call s.vars ;
      init = INIT.havoc_any ~call s.init ;
      mem = M.Sigma.havoc_any ~call s.mem ;
    }

    let remove_chunks s r =
      let rvar , rinit, ralloc , rmem = domain_partition r
      in {
        vars = SIGMA.remove_chunks s.vars rvar ;
        init = INIT.remove_chunks s.init rinit ;
        alloc = ALLOC.remove_chunks s.alloc ralloc ;
        mem = M.Sigma.remove_chunks s.mem rmem ;
      }

    let domain s =
      Heap.Set.union
        (Heap.Set.union
           (Heap.Set.union
              (domain_var (SIGMA.domain s.vars))
              (domain_alloc (ALLOC.domain s.alloc)))
           (domain_mem (M.Sigma.domain s.mem)))
        (domain_init (INIT.domain s.init))

    let writes s =
      Heap.Set.union
        (Heap.Set.union
           (Heap.Set.union
              (domain_var (SIGMA.writes {pre=s.pre.vars;post=s.post.vars}))
              (domain_alloc (ALLOC.writes {pre=s.pre.alloc;post=s.post.alloc})))
           (domain_mem (M.Sigma.writes {pre=s.pre.mem;post=s.post.mem})))
        (domain_init (INIT.writes {pre=s.pre.init;post=s.post.init}))

    let pretty fmt s =
      Format.fprintf fmt "@[<hov 2>{X:@[%a@]@ I:@[%a@]@ T:@[%a@]@ M:@[%a@]}@]"
        SIGMA.pretty s.vars
        INIT.pretty s.init
        ALLOC.pretty s.alloc
        M.Sigma.pretty s.mem

  end

  type domain = Sigma.domain

  let get_var s x = SIGMA.get s.vars x
  let get_term s x = e_var (get_var s x)

  let get_init s x = INIT.get s.init x
  let get_init_term s x = e_var (get_init s x)

  (* -------------------------------------------------------------------------- *)
  (* ---  State Pretty Printer                                              --- *)
  (* -------------------------------------------------------------------------- *)

  type ichunk = Iref of varinfo | Ivar of varinfo | Iinit of varinfo

  type state = {
    svar : ichunk Tmap.t ;
    smem : M.state ;
  }

  module IChunk =
  struct
    let compare_var x y =
      let rank x =
        if x.vformal then 0 else
        if x.vglob then 1 else
        if x.vtemp then 3 else 2 in
      let cmp = rank x - rank y in
      if cmp <> 0 then cmp else Varinfo.compare x y

    type t = ichunk
    let hash = function
      | Iref x | Ivar x -> Varinfo.hash x
      | Iinit x -> 13 * Varinfo.hash x
    let compare x y =
      match x,y with
      | Iref x , Iref y -> Varinfo.compare x y
      | Ivar x , Ivar y -> compare_var x y
      | Iinit x, Iinit y -> compare_var x y
      | Iref _ , _ -> (-1)
      | Iinit _, _ -> (-1)
      | _ , Iref _ -> 1
      | _ , Iinit _ -> 1

    let equal x y =
      match x,y with
      | Iref x, Iref y | Ivar x, Ivar y | Iinit x, Iinit y -> Varinfo.equal x y
      |  _, _ -> false

  end

  module Icmap = Qed.Mergemap.Make(IChunk)

  let set_chunk v c m =
    let c =
      try
        let c0 = Tmap.find v m in
        if IChunk.compare c c0 < 0 then c else c0
      with Not_found -> c in
    Tmap.add v c m

  let state s =
    let m = ref Tmap.empty in
    SIGMA.iter (fun x v ->
        let c = match V.param x with ByRef -> Iref x | _ -> Ivar x in
        m := set_chunk (e_var v) c !m
      ) s.vars ;
    INIT.iter (fun x v ->
        match V.param x with
        | ByRef -> ()
        | _ -> m := set_chunk (e_var v) (Iinit x) !m
      ) s.init ;
    { svar = !m ; smem = M.state s.mem }

  let ilval = function
    | Iref x -> (Mvar x,[Mindex e_zero])
    | Ivar x | Iinit x -> (Mvar x,[])

  let imval c = match c with
    | Iref _ | Ivar _ -> Sigs.Mlval (ilval c, KValue)
    | Iinit _ -> Sigs.Mlval (ilval c, KInit)

  let lookup s e =
    try imval (Tmap.find e s.svar)
    with Not_found -> M.lookup s.smem e

  let apply f s =
    let m = ref Tmap.empty in
    Tmap.iter (fun e c ->
        let e = f e in
        m := set_chunk e c !m ;
      ) s.svar ;
    { svar = !m ; smem = M.apply f s.smem }

  let iter f s =
    Tmap.iter (fun v c -> f (imval c) v) s.svar ;
    M.iter f s.smem

  let icmap domain istate =
    Tmap.fold (fun m c w ->
        if Vars.intersect (F.vars m) domain
        then Icmap.add c m w else w
      ) istate Icmap.empty

  let rec diff lv v1 v2 =
    if v1 == v2 then Bag.empty else
      match F.repr v2 with
      | Qed.Logic.Aset(m , k , vk) ->
        let upd = diff (Mstate.index lv k) (F.e_get m k) vk in
        Bag.concat (diff lv v1 m) upd
      | Qed.Logic.Rdef fvs ->
        rdiff lv v1 v2 fvs
      | _ ->
        Bag.elt (Mstore(lv,v2))

  and rdiff lv v1 v2 = function
    | (Lang.Cfield (fi, _k) as fd ,f2) :: fvs ->
      let f1 = F.e_getfield v1 fd in
      if f1 == f2 then rdiff lv v1 v2 fvs else
        let upd = diff (Mstate.field lv fi) f1 f2 in
        let m = F.e_setfield v2 fd f1 in
        Bag.concat upd (diff lv v1 m)
    | (Lang.Mfield _,_)::_ -> Bag.elt (Mstore(lv,v2))
    | [] -> Bag.empty

  let updates seq domain =
    let pre = icmap domain seq.pre.svar in
    let post = icmap domain seq.post.svar in
    let pool = ref Bag.empty in
    Icmap.iter2
      (fun c v1 v2 ->
         match v1 , v2 with
         | _ , None -> ()
         | None , Some v -> pool := Bag.add (Mstore(ilval c,v)) !pool
         | Some v1 , Some v2 -> pool := Bag.concat (diff (ilval c) v1 v2) !pool
      ) pre post ;
    let seq_mem =  { pre = seq.pre.smem ; post = seq.post.smem } in
    Bag.concat !pool (M.updates seq_mem domain)

  (* -------------------------------------------------------------------------- *)
  (* ---  Location                                                          --- *)
  (* -------------------------------------------------------------------------- *)

  type mem =
    | CVAL (* By-Value variable *)
    | CREF (* By-Ref variable *)
    | CTXT of MemoryContext.validity (* In-context pointer *)
    | CARR of MemoryContext.validity (* In-context array *)
    | HEAP (* In-heap variable *)

  type loc =
    | Ref of varinfo
    | Val of mem * varinfo * ofs list (* The varinfo has {i not} been contextualized yet *)
    | Loc of M.loc (* Generalized In-Heap pointer *)

  and ofs =
    | Field of fieldinfo
    | Shift of c_object * term

  type segment = loc rloc

  let rec ofs_vars xs = function
    | [] -> xs
    | Field _ :: ofs -> ofs_vars xs ofs
    | Shift(_,k) :: ofs -> ofs_vars (Vars.union xs (F.vars k)) ofs

  let vars = function
    | Ref _ -> Vars.empty
    | Loc l -> M.vars l
    | Val(_,_,ofs) -> ofs_vars Vars.empty ofs

  let rec ofs_occurs x = function
    | [] -> false
    | Field _ :: ofs -> ofs_occurs x ofs
    | Shift(_,k) :: ofs -> Vars.mem x (F.vars k) || ofs_occurs x ofs

  let occurs x = function
    | Ref _ -> false
    | Loc l -> M.occurs x l
    | Val(_,_,ofs) -> ofs_occurs x ofs

  let byte_offset n = function
    | Field fd -> F.e_add n (F.e_int (Ctypes.field_offset fd))
    | Shift(obj,k) -> F.e_add n (F.e_fact (Ctypes.sizeof_object obj) k)

  (* -------------------------------------------------------------------------- *)
  (* ---  Variable and Context                                              --- *)
  (* -------------------------------------------------------------------------- *)

  let vtype m x =
    match m with
    | CVAL | HEAP -> x.vtype
    | CTXT _ | CREF -> Cil.typeOf_pointed x.vtype
    | CARR _ -> Ast_info.array_type (Cil.typeOf_pointed x.vtype)

  let vobject m x = Ctypes.object_of (vtype m x)

  let vbase m x =
    match m with
    | CVAL | HEAP -> x
    | _ -> { x with vglob = true ; vtype = vtype m x }

  (* -------------------------------------------------------------------------- *)
  (* ---  Pretty                                                            --- *)
  (* -------------------------------------------------------------------------- *)

  let rec pp_offset ~obj fmt = function
    | [] -> ()
    | Field f :: ofs ->
      Format.fprintf fmt ".%s" f.fname ;
      pp_offset ~obj:(object_of f.ftype) fmt ofs
    | Shift(elt,k) :: ofs ->
      if Ctypes.is_array obj ~elt then
        ( Format.fprintf fmt ".(%a)" F.pp_term k ;
          pp_offset ~obj:elt fmt ofs )
      else
        ( Format.fprintf fmt ".(%a : %a)" F.pp_term k Ctypes.pretty elt ;
          pp_offset ~obj:elt fmt ofs )

  let pp_mem fmt = function
    | CVAL -> Format.pp_print_string fmt "var"
    | CREF -> Format.pp_print_string fmt "ref"
    | CTXT _ -> Format.pp_print_string fmt "ptr"
    | CARR _ -> Format.pp_print_string fmt "arr"
    | HEAP -> Format.pp_print_string fmt "mem"

  (* re-uses strings that are used into the description of -wp-xxx-vars *)
  let pp_var_model fmt = function
    | ByValue | ByShift | NotUsed -> Format.pp_print_string fmt "non-aliased" (* cf.  -wp-unalias-vars *)
    | ByRef -> Format.pp_print_string fmt "by reference" (* cf. -wp-ref-vars *)
    | InContext _ | InArray _ -> Format.pp_print_string fmt "in an isolated context" (* cf. -wp-context-vars *)
    | ByAddr -> Format.pp_print_string fmt "aliased" (* cf. -wp-alias-vars *)

  let pretty fmt = function
    | Ref x -> VAR.pretty fmt x
    | Loc l -> M.pretty fmt l
    | Val(m,x,ofs) ->
      let obj = vobject m x in
      Format.fprintf fmt "@[%a:%a%a@]"
        pp_mem m VAR.pretty x
        (pp_offset ~obj) ofs

  let noref ~op var =
    Warning.error
      "forbidden %s variable '%a' considered %a.@\n\
       Use model 'Typed' instead or specify '-wp-unalias-vars %a'"
      op Varinfo.pretty var
      pp_var_model (V.param var)
      Varinfo.pretty var

  (* -------------------------------------------------------------------------- *)
  (* ---  Basic Constructors                                                --- *)
  (* -------------------------------------------------------------------------- *)

  let null = Loc M.null

  let literal ~eid cst = Loc (M.literal ~eid cst)

  let cvar x = match V.param x with
    | NotUsed | ByValue | ByShift -> Val(CVAL,x,[])
    | ByAddr -> Val(HEAP,x,[])
    | InContext _ | InArray _ | ByRef -> Ref x

  (* -------------------------------------------------------------------------- *)
  (* ---  Nullable locations                                                --- *)
  (* -------------------------------------------------------------------------- *)

  module Nullable = WpContext.Generator(Varinfo)
      (struct
        let name = "MemVar.Nullable"
        type key = varinfo
        type data = lfun

        let compile v =
          let result = t_addr () in
          let lfun = Lang.generated_f ~result "pointer_%s" v.vname in
          let cluster =
            Definitions.cluster ~id:"Globals" ~title:"Context pointers" ()
          in
          Definitions.define_symbol {
            d_lfun = lfun ;
            d_types = 0 ;
            d_params = [] ;
            d_definition = Definitions.Logic result ;
            d_cluster = cluster
          } ;
          lfun
      end)

  let nullable_address v =
    Lang.F.e_fun (Nullable.get v) []

  (* -------------------------------------------------------------------------- *)
  (* ---  Lifting                                                           --- *)
  (* -------------------------------------------------------------------------- *)

  let moffset l = function
    | Field f -> M.field l f
    | Shift(e,k) -> M.shift l e k

  let mseq_of_seq seq = { pre = seq.pre.mem ; post = seq.post.mem }
  let mloc_of_path m x ofs =
    let l = match m with
      | CTXT Nullable | CARR Nullable -> M.pointer_loc @@ nullable_address x
      | _ -> M.cvar (vbase m x)
    in
    List.fold_left moffset l ofs

  let mloc_of_loc = function
    | Loc l -> l
    | Ref x -> M.cvar x
    | Val(m,x,ofs) -> mloc_of_path m x ofs

  let pointer_loc p = Loc (M.pointer_loc p)
  let pointer_val l = M.pointer_val (mloc_of_loc l)

  let field l f = match l with
    | Loc l -> Loc (M.field l f)
    | Ref x -> noref ~op:"field access to" x
    | Val(m,x,ofs) -> Val(m,x,ofs @ [Field f])

  let rec ofs_shift obj k = function
    | [] -> [Shift(obj,k)]
    | [Shift(elt,i)] when Ctypes.equal obj elt -> [Shift(elt,F.e_add i k)]
    | f::ofs -> f :: ofs_shift obj k ofs

  let shift l obj k = match l with
    | Loc l -> Loc (M.shift l obj k)
    | Ref x -> noref ~op:"array access to" x
    | Val(m,x,ofs) -> Val(m,x,ofs_shift obj k ofs)

  let base_addr = function
    | Loc l -> Loc (M.base_addr l)
    | Ref x -> noref ~op:"base address of" x (* ??? ~suggest:ByValue *)
    | Val(m,x,_) -> Val(m,x,[])

  let base_offset = function
    | Loc l -> M.base_offset l
    | Ref x -> noref ~op:"offset address of" x (* ??? ~suggest:ByValue *)
    | Val(_,_,ofs) -> List.fold_left byte_offset e_zero ofs

  let block_length sigma obj = function
    | Loc l -> M.block_length sigma.mem obj l
    | Ref x -> noref ~op:"block-length of" x
    | Val(m,x,_) ->
      begin match Ctypes.object_of (vtype m x) with
        | C_comp ({ cfields = None } as c) ->
          Cvalues.bytes_length_of_opaque_comp c
        | obj ->
          let size =
            if Ctypes.sizeof_defined obj
            then Ctypes.sizeof_object obj
            else if Wp_parameters.ExternArrays.get ()
            then max_int
            else Warning.error ~source:"MemVar" "Unknown array-size"
          in F.e_int size
      end

  let cast obj l = Loc(M.cast obj (mloc_of_loc l))
  let loc_of_int e a = Loc(M.loc_of_int e a)
  let int_of_loc i l = M.int_of_loc i (mloc_of_loc l)

  (* -------------------------------------------------------------------------- *)
  (* ---  Memory Load                                                       --- *)
  (* -------------------------------------------------------------------------- *)

  let rec access_gen kind a = function
    | [] -> a
    | Field f :: ofs -> access_gen kind (e_getfield a (Cfield (f, kind))) ofs
    | Shift(_,k) :: ofs -> access_gen kind (e_get a k) ofs
  let access = access_gen KValue
  let access_init = access_gen KInit

  let rec update_gen kind a ofs v = match ofs with
    | [] -> v
    | Field f :: ofs ->
      let phi = Cfield (f, kind) in
      let a_f = F.e_getfield a phi in
      let a_f_v = update_gen kind a_f ofs v in
      F.e_setfield a phi a_f_v
    | Shift(_,k) :: ofs ->
      let a_k = F.e_get a k in
      let a_k_v = update_gen kind a_k ofs v in
      F.e_set a k a_k_v

  let update = update_gen KValue
  let update_init = update_gen KInit

  let load sigma obj = function
    | Ref x ->
      begin match V.param x with
        | ByRef       -> Sigs.Loc(Val(CREF,x,[]))
        | InContext n -> Sigs.Loc(Val(CTXT n,x,[]))
        | InArray n   -> Sigs.Loc(Val(CARR n,x,[]))
        | NotUsed | ByAddr | ByValue | ByShift -> assert false
      end
    | Val((CREF|CVAL),x,ofs) ->
      Sigs.Val(access (get_term sigma x) ofs)
    | Loc l ->
      Cvalues.map_value
        (fun l -> Loc l)
        (M.load sigma.mem obj l)
    | Val((CTXT _|CARR _|HEAP) as m,x,ofs) ->
      Cvalues.map_value
        (fun l -> Loc l)
        (M.load sigma.mem obj (mloc_of_path m x ofs))

  let load_init sigma obj = function
    | Ref _ ->
      e_true
    | Val((CREF|CVAL),x,_) when Cvalues.always_initialized x ->
      Cvalues.initialized_obj obj
    | Val((CREF|CVAL),x,ofs) ->
      access_init (get_init_term sigma x) ofs
    | Loc l ->
      M.load_init sigma.mem obj l
    | Val((CTXT _|CARR _|HEAP) as m,x,ofs) ->
      M.load_init sigma.mem obj (mloc_of_path m x ofs)

  (* -------------------------------------------------------------------------- *)
  (* ---  Memory Store                                                      --- *)
  (* -------------------------------------------------------------------------- *)

  let memvar_stored kind seq x ofs v =
    let get_term, update = match kind with
      | KValue -> get_term, update
      | KInit -> get_init_term, update_init
    in
    let v1 = get_term seq.pre x in
    let v2 = get_term seq.post x in
    Set( v2 , update v1 ofs v )

  let gen_stored kind seq obj l v =
    let mstored = match kind with KValue -> M.stored | KInit -> M.stored_init in
    match l with
    | Ref x -> noref ~op:"write to" x
    | Val((CREF|CVAL),x,ofs) ->
      [memvar_stored kind seq x ofs v]
    | Val((CTXT _|CARR _|HEAP) as m,x,ofs) ->
      mstored (mseq_of_seq seq) obj (mloc_of_path m x ofs) v
    | Loc l ->
      mstored (mseq_of_seq seq) obj l v

  let stored = gen_stored KValue
  let stored_init = gen_stored KInit

  let copied seq obj l1 l2 =
    let v = match load seq.pre obj l2 with
      | Sigs.Val r -> r
      | Sigs.Loc l -> pointer_val l
    in stored seq obj l1 v

  let copied_init seq obj l1 l2 =
    stored_init seq obj l1 (load_init seq.pre obj l2)

  (* -------------------------------------------------------------------------- *)
  (* ---  Pointer Comparison                                                --- *)
  (* -------------------------------------------------------------------------- *)

  let is_null = function
    | Loc l -> M.is_null l
    | Val ((CTXT Nullable|CARR Nullable)as m,x,ofs) ->
      M.is_null (mloc_of_path m x ofs)
    | Ref _ | Val _ -> F.p_false

  let rec offset = function
    | [] -> e_zero
    | Field f :: ofs ->
      e_add (e_int (Ctypes.field_offset f)) (offset ofs)
    | Shift(obj,k)::ofs ->
      e_add (e_fact (Ctypes.sizeof_object obj) k) (offset ofs)

  let loc_diff obj a b =
    match a , b with
    | Loc l1 , Loc l2 -> M.loc_diff obj l1 l2
    | Ref x , Ref y when Varinfo.equal x y -> e_zero
    | Val(_,x,p) , Val(_,y,q) when Varinfo.equal x y ->
      e_div (e_sub (offset p) (offset q)) (e_int (Ctypes.sizeof_object obj))
    | _ ->
      Warning.error ~source:"Reference Variable Model"
        "Uncomparable locations %a and %a" pretty a pretty b

  let loc_compare lcmp icmp same a b =
    match a , b with
    | Loc l1 , Loc l2 -> lcmp l1 l2
    | Ref x , Ref y ->
      if Varinfo.equal x y then same else p_not same
    | Val(_,x,p) , Val(_,y,q) ->
      if Varinfo.equal x y then icmp (offset p) (offset q) else p_not same
    | (Val _ | Loc _) , (Val _ | Loc _) -> lcmp (mloc_of_loc a) (mloc_of_loc b)
    | Ref _ , (Val _ | Loc _) | (Val _ | Loc _) , Ref _ -> p_not same

  let loc_eq = loc_compare M.loc_eq F.p_equal F.p_true
  let loc_lt = loc_compare M.loc_lt F.p_lt F.p_false
  let loc_leq = loc_compare M.loc_leq F.p_leq F.p_true
  let loc_neq = loc_compare M.loc_neq F.p_neq F.p_false

  (* -------------------------------------------------------------------------- *)
  (* ---  Range & Offset Fits                                               --- *)
  (* -------------------------------------------------------------------------- *)

  exception ShiftMismatch

  let is_heap_allocated = function
    | CREF | CVAL -> false | HEAP | CTXT _ | CARR _ -> true

  let shift_mismatch l =
    Wp_parameters.fatal "Invalid shift : %a" pretty l

  let unsized_array () = Warning.error ~severe:false
      "Validity of unsized-array not implemented yet"

  let fits_inside cond a b n =
    p_leq e_zero a :: p_lt b (e_int n) :: cond

  let fits_off_by_one cond a b n =
    p_leq e_zero a :: p_leq b (e_int n) :: cond

  let stay_outside cond a b n =
    p_lt b e_zero :: p_leq (e_int n) a :: cond

  (* Append conditions to [cond] for [range=(elt,a,b)],
     consisting of [a..b] elements with type [elt] to fits inside the block,
     provided [a<=b]. *)
  let rec block_check fitting cond (block,size) ((elt,a,b) as range) =
    if Ctypes.equal block elt then
      fitting cond a b size
    else
      match Ctypes.get_array block with
      | Some( e , Some n ) -> block_check fitting cond (e , n * size) range
      | Some( _ , None ) -> unsized_array ()
      | None -> raise ShiftMismatch

  (* Append conditions for and array offset (te,k) to fits in obj *)
  let array_check fitting cond te k obj =
    match Ctypes.get_array obj with
    | Some( e , Some n ) when Ctypes.equal e te -> fitting cond k k n
    | Some( _ , None ) -> unsized_array ()
    | _ -> block_check fitting cond (obj,1) (te,k,k)

  (* Append conditions for [offset] to fits [object], provided [a<=b]. *)
  let rec offset_fits cond obj offset =
    match offset with
    | [] -> cond
    | Field fd :: ofs ->
      offset_fits cond (Ctypes.object_of fd.ftype) ofs
    | Shift(te,k) :: ofs ->
      let cond = array_check fits_inside cond te k obj in
      offset_fits cond te ofs

  (* Append conditions to [cond] for [range=(elt,a,b)], starting at [offset],
     consisting of [a..b] elements with type [elt] to fits inside the block,
     of stay outside valid paths, provided [a<=b]. *)
  let rec range_check fitting cond alloc offset ((elt,a,b) as range) =
    match offset with
    | [] -> block_check fitting cond alloc range
    | Field fd :: ofs ->
      range_check fitting cond (Ctypes.object_of fd.ftype,1) ofs range
    | Shift(te,k) :: ofs ->
      if Ctypes.equal te elt then
        range_check fitting cond alloc ofs (elt,e_add a k,e_add b k)
      else
        match Ctypes.get_array (fst alloc) with
        | Some( e , Some n ) when Ctypes.equal e te ->
          let cond = fitting cond k k n in
          range_check fitting cond (e,n) ofs range
        | Some( _ , None ) ->
          unsized_array ()
        | _ ->
          let cond = block_check fitting cond alloc (te,k,k) in
          range_check fitting cond (te,1) ofs range

  (* -------------------------------------------------------------------------- *)
  (* ---  Validity                                                          --- *)
  (* -------------------------------------------------------------------------- *)

  let rec last_field_shift acs obj ofs =
    match acs , obj , ofs with
    | OBJ , _ , [Shift(te,k)] -> Some(te,k,obj)
    | OBJ , C_comp c , (Field fd :: ofs) ->
      begin
        match Option.map List.rev c.cfields with
        | Some (fd0::_) when Fieldinfo.equal fd fd0 ->
          last_field_shift acs (Ctypes.object_of fd.ftype) ofs
        | _ -> None
      end
    | _ -> None

  let valid_offset acs obj ofs =
    match last_field_shift acs obj ofs with
    | Some(te,k,obj) ->
      F.p_conj (array_check fits_off_by_one [] te k obj)
    | None ->
      F.p_conj (offset_fits [] obj ofs)

  let valid_range acs obj ofs range =
    match last_field_shift acs obj ofs with
    | Some _ ->
      F.p_conj (range_check fits_off_by_one [] (obj,1) ofs range)
    | _ ->
      F.p_conj (range_check fits_inside [] (obj,1) ofs range)

  (* varinfo *)

  let valid_base sigma acs mem x =
    if x.vglob then
      match acs with
      | RW -> if Cil.typeHasQualifier "const" x.vtype then p_false else p_true
      | RD | OBJ -> p_true
    else
      match mem with
      | CVAL | HEAP -> p_bool (ALLOC.value sigma.alloc x)
      | CREF | CTXT Valid | CARR Valid -> p_true
      | CTXT Nullable | CARR Nullable ->
        p_not @@ M.is_null (mloc_of_path mem x [])

  (* segment *)

  let valid_offset_path sigma acs mem x ofs =
    p_and
      (valid_base sigma acs mem x)
      (valid_offset acs (vobject mem x) ofs)

  let valid_range_path sigma acs mem x ofs rg =
    p_and
      (valid_base sigma acs mem x)
      (valid_range acs (vobject mem x) ofs rg)

  (* in-model validation *)

  let valid sigma acs = function
    | Rloc(obj,l) ->
      begin match l with
        | Ref _ -> p_true
        | Loc l -> M.valid sigma.mem acs (Rloc(obj,l))
        | Val(m,x,p) ->
          try valid_offset_path sigma acs m x p
          with ShiftMismatch ->
            if is_heap_allocated m then
              M.valid sigma.mem acs (Rloc(obj,mloc_of_loc l))
            else
              shift_mismatch l
      end
    | Rrange(l,elt,a,b) ->
      begin match l with
        | Ref x -> noref ~op:"valid sub-range of" x
        | Loc l -> M.valid sigma.mem acs (Rrange(l,elt,a,b))
        | Val(m,x,p) ->
          match a,b with
          | Some ka,Some kb ->
            begin
              try
                F.p_imply (F.p_leq ka kb)
                  (valid_range_path sigma acs m x p (elt,ka,kb))
              with ShiftMismatch ->
                if is_heap_allocated m then
                  let l = mloc_of_loc l in
                  M.valid sigma.mem acs (Rrange(l,elt,a,b))
                else shift_mismatch l
            end
          | _ ->
            Warning.error "Validity of infinite range @[%a.(%a..%a)@]"
              pretty l Vset.pp_bound a Vset.pp_bound b
      end

  (* -------------------------------------------------------------------------- *)
  (* ---  Invalidity                                                        --- *)
  (* -------------------------------------------------------------------------- *)

  let invalid_range obj ofs range =
    F.p_disj (range_check stay_outside [] (obj,1) ofs range)

  let invalid_offset_path sigma m x p =
    p_not (valid_offset_path sigma RD m x p)

  let invalid_range_path sigma m x p rg =
    p_imply
      (valid_base sigma RD m x)
      (invalid_range (vobject m x) p rg)

  let invalid sigma = function
    | Rloc(obj,l) ->
      begin match l with
        | Ref _ -> p_false
        | Loc l -> M.invalid sigma.mem (Rloc(obj,l))
        | Val(m,x,p) ->
          try invalid_offset_path sigma m x p
          with ShiftMismatch ->
            if is_heap_allocated m then
              M.invalid sigma.mem (Rloc(obj,mloc_of_loc l))
            else
              shift_mismatch l
      end
    | Rrange(l,elt,a,b) ->
      begin match l with
        | Ref x -> noref ~op:"invalid sub-range of" x
        | Loc l -> M.invalid sigma.mem (Rrange(l,elt,a,b))
        | Val(m,x,p) ->
          match a,b with
          | Some ka,Some kb ->
            begin
              try
                F.p_imply (F.p_leq ka kb)
                  (invalid_range_path sigma m x p (elt,ka,kb))
              with ShiftMismatch ->
                if is_heap_allocated m then
                  let l = mloc_of_loc l in
                  M.invalid sigma.mem (Rrange(l,elt,a,b))
                else shift_mismatch l
            end
          | _ ->
            Warning.error "Invalidity of infinite range @[%a.(%a..%a)@]"
              pretty l Vset.pp_bound a Vset.pp_bound b
      end

  (* -------------------------------------------------------------------------- *)
  (* ---  Initialized                                                       --- *)
  (* -------------------------------------------------------------------------- *)

  let rec initialized_loc sigma obj x ofs =
    match obj with
    | C_int _ | C_float _ | C_pointer _ ->
      p_bool (access_init (get_init_term sigma x) ofs)
    | C_array { arr_flat=flat ; arr_element = te } ->
      let size = match flat with
        | None -> unsized_array ()
        | Some { arr_size } -> arr_size in
      let elt = Ctypes.object_of te in
      initialized_range sigma elt x ofs (e_int 0) (e_int (size-1))
    | C_comp { cfields = None } ->
      Lang.F.p_equal
        (access_init (get_init_term sigma x) ofs)
        (Cvalues.initialized_obj obj)
    | C_comp { cfields = Some fields } ->
      let init_field fd =
        let obj_fd = Ctypes.object_of fd.ftype in
        let ofs_fd = ofs @ [Field fd] in
        initialized_loc sigma obj_fd x ofs_fd
      in
      Lang.F.p_conj (List.map init_field fields)

  and initialized_range sigma elt x ofs lo up =
    let i = Lang.freshvar ~basename:"i" Lang.t_int in
    let vi = e_var i in
    let ofs = ofs @ [ Shift(elt, vi) ] in
    let range_i = p_and (p_leq lo vi) (p_leq vi up) in
    let init_i = initialized_loc sigma elt x ofs in
    Lang.F.p_forall [i] (p_imply range_i init_i)

  let initialized sigma l =
    match l with
    | Rloc(obj,l) ->
      begin match l with
        | Ref _ -> p_true
        | Loc l -> M.initialized sigma.mem (Rloc(obj,l))
        | Val(m,x,p) ->
          if is_heap_allocated m then
            M.initialized sigma.mem (Rloc(obj,mloc_of_loc l))
          else if Cvalues.always_initialized x then
            try valid_offset RD (vobject m x) p
            with ShiftMismatch -> shift_mismatch l
          else
            initialized_loc sigma obj x p
      end
    | Rrange(l,elt, Some a, Some b) ->
      begin match l with
        | Ref _ -> p_true
        | Loc l -> M.initialized sigma.mem (Rrange(l,elt,Some a, Some b))
        | Val(m,x,p) ->
          try
            if is_heap_allocated m then
              let l = mloc_of_loc l in
              M.initialized sigma.mem (Rrange(l,elt,Some a, Some b))
            else
              let rec normalize obj = function
                | [] -> [], a, b
                | [Shift(elt, i)] when Ctypes.equal obj elt ->
                  [], F.e_add a i, F.e_add b i
                | f :: ofs ->
                  let l, a, b = normalize obj ofs in f :: l, a, b
              in
              let p, a, b = normalize elt p in
              let in_array = valid_range RD (vobject m x) p (elt, a, b) in
              let initialized =
                if Cvalues.always_initialized x then p_true
                else initialized_range sigma elt x p a b
              in
              F.p_imply (F.p_leq a b) (p_and in_array initialized)
          with ShiftMismatch ->
            shift_mismatch l
      end
    | Rrange(l, _,a,b) ->
      Warning.error
        "Initialization of infinite range @[%a.(%a..%a)@]"
        pretty l Vset.pp_bound a Vset.pp_bound b


  (* -------------------------------------------------------------------------- *)
  (* ---  Framing                                                           --- *)
  (* -------------------------------------------------------------------------- *)

  let rec forall_pointers phi v t =
    match Cil.unrollType t with
    | TInt _ | TFloat _ | TVoid _ | TEnum _ | TNamed _ | TBuiltin_va_list _
      -> F.p_true
    | TPtr _ | TFun _ -> phi v
    | TComp({ cfields = None },_) ->
      F.p_true
    | TComp({ cfields = Some fields },_) ->
      F.p_all
        (fun fd ->
           forall_pointers phi (e_getfield v (Cfield (fd, KValue))) fd.ftype)
        fields
    | TArray(elt,_,_) ->
      let k = Lang.freshvar Qed.Logic.Int in
      F.p_forall [k] (forall_pointers phi (e_get v (e_var k)) elt)

  let frame sigma =
    let hs = ref [] in
    SIGMA.iter
      begin fun x chunk ->
        (if (x.vglob || x.vformal) then
           let t = VAR.typ_of_chunk x in
           let v = e_var chunk in
           let h = forall_pointers (M.global sigma.mem) v t in
           if not (F.eqp h F.p_true) then hs := h :: !hs ) ;
        (if x.vglob then
           let v = e_var chunk in
           hs := Cvalues.has_ctype x.vtype v :: !hs ) ;
      end sigma.vars ;
    !hs @ M.frame sigma.mem

  (* -------------------------------------------------------------------------- *)
  (* ---  Scope                                                             --- *)
  (* -------------------------------------------------------------------------- *)

  let is_mem x = match V.param x with
    | ByAddr -> true
    | _ -> false

  let is_mvar_alloc x =
    match V.param x with
    | ByRef | InContext _ | InArray _ | NotUsed -> false
    | ByValue | ByShift | ByAddr -> true

  let alloc sigma xs =
    let xm = List.filter is_mem xs in
    let mem = M.alloc sigma.mem xm in
    let xv = List.filter is_mvar_alloc xs in
    let domain = TALLOC.Set.of_list xv in
    let alloc = ALLOC.havoc sigma.alloc domain in
    { sigma with alloc ; mem }

  let scope_vars seq scope xs =
    let xs = List.filter is_mvar_alloc xs in
    if xs = [] then []
    else
      let t_in = seq.pre.alloc in
      let t_out = seq.post.alloc in
      let v_in  = match scope with Enter -> e_false | Leave -> e_true in
      let v_out = match scope with Enter -> e_true | Leave -> e_false in
      List.map
        (fun x ->
           F.p_and
             (F.p_equal (ALLOC.value t_in x) v_in)
             (F.p_equal (ALLOC.value t_out x) v_out)
        ) xs

  let scope_init seq scope xs =
    match scope with
    | Leave -> []
    | Enter ->
      let init_status v =
        if v.vdefined || Cvalues.always_initialized v || not@@ is_mvar_alloc v
        then None
        else
          let i = Cvalues.uninitialized_obj (Ctypes.object_of v.vtype) in
          Some (Lang.F.p_equal (access_init (get_init_term seq.post v) []) i)
      in
      List.filter_map init_status xs

  let is_nullable m v =
    let addr = nullable_address v in
    p_or
      (M.is_null @@ M.pointer_loc addr)
      (p_equal (M.pointer_val @@ M.cvar (vbase m v)) addr)

  let nullable_scope v =
    match V.param v with
    | InContext Nullable -> Some (is_nullable (CTXT Nullable) v)
    | InArray Nullable -> Some (is_nullable (CARR Nullable) v)
    | _ -> None

  let scope seq scope xs =
    let xm = List.filter is_mem xs in
    let smem = { pre = seq.pre.mem ; post = seq.post.mem } in
    let hmem = M.scope smem scope xm in
    let hvars = scope_vars seq scope xs in
    let hinit = scope_init seq scope xs in
    let hcontext = List.filter_map nullable_scope xs in
    hvars @ hinit @ hmem @ hcontext

  let global sigma p = M.global sigma.mem p

  (* -------------------------------------------------------------------------- *)
  (* ---  Havoc along a ranged-path                                        --- *)
  (* -------------------------------------------------------------------------- *)

  let rec assigned_path
      kind
      (hs : pred list) (* collector of properties *)
      (xs : var list)  (* variable quantifying the assigned location *)
      (ys : var list)  (* variable quantifying others locations *)
      (a : term)  (* pre-term for root + current offset *)
      (b : term)  (* post-term for root + current offset *)
    = function
      | [] -> hs

      (*TODO: optimized version for terminal [Field _] and [Index _] *)

      | Field f :: ofs ->
        let cf = Cfield (f, kind) in
        let af = e_getfield a cf in
        let bf = e_getfield b cf in
        let hs = assigned_path kind hs xs ys af bf ofs in
        List.fold_left
          (fun hs g ->
             if Fieldinfo.equal f g then hs else
               let cg = Cfield (g, kind) in
               let ag = e_getfield a cg in
               let bg = e_getfield b cg in
               let eqg = p_forall ys (p_equal ag bg) in
               eqg :: hs
          ) hs
          (* Note: we have field accesses, everything here is thus complete *)
          (Option.get f.fcomp.cfields)

      | Shift(_,e) :: ofs ->
        let y = Lang.freshvar ~basename:"k" Qed.Logic.Int in
        let k = e_var y in
        let ak = e_get a k in
        let bk = e_get b k in
        if List.exists (fun x -> F.occurs x e) xs then
          (* index [e] is covered by [xs]:
             must explore deeper the remaining path. *)
          assigned_path kind hs xs (y::ys) ak bk ofs
        else
          (* index [e] is not covered by [xs]:
             any index different from e is disjoint.
             explore also deeply with index [e]. *)
          let ae = e_get a e in
          let be = e_get b e in
          let ek = p_neq e k in
          let eqk = p_forall (y::ys) (p_imply ek (p_equal ak bk)) in
          assigned_path kind (eqk :: hs) xs ys ae be ofs

  let assigned_genset s xs mem x ofs p =
    let valid = valid_offset_path s.post Sigs.RW mem x ofs in
    let a = get_term s.pre x in
    let b = get_term s.post x in
    let a_ofs = access a ofs in
    let b_ofs = access b ofs in
    let p_sloc = p_forall xs (p_hyps [valid;p_not p] (p_equal a_ofs b_ofs)) in
    let conds = assigned_path KValue [p_sloc] xs [] a b ofs in
    List.map (fun p -> Assert p) conds

  (*
  let monotonic_initialized_genset s xs mem x ofs p =
    if always_init x then [Assert p_true]
    else
      let valid = valid_offset_path s.post Sigs.RW mem x ofs in
      let a = get_init_term s.pre x in
      let b = get_init_term s.post x in
      let a_ofs = access_init a ofs in
      let b_ofs = access_init b ofs in
      let not_p = p_forall xs (p_hyps [valid;p_not p] (p_equal a_ofs b_ofs)) in
      let exact_p =
        p_forall xs (p_hyps [valid; p] (p_imply (p_bool a_ofs) (p_bool b_ofs)))
      in
      let conds = assigned_path KInit [not_p; exact_p] xs [] a b ofs in
      List.map (fun p -> Assert p) conds
  *)

  (* -------------------------------------------------------------------------- *)
  (* ---  Assigned                                                          --- *)
  (* -------------------------------------------------------------------------- *)

  let rec monotonic_initialized seq obj x ofs =
    if Cvalues.always_initialized x then p_true
    else
      match obj with
      (* Structure initialization is not monotonic *)
      | C_comp _ -> p_true
      (* Neither is initialization of arrays of structures *)
      | C_array { arr_element=t } when Cil.isStructOrUnionType t -> p_true

      | C_int _ | C_float _ | C_pointer _ ->
        p_imply
          (p_bool (access_init (get_init_term seq.pre x) ofs))
          (p_bool (access_init (get_init_term seq.post x) ofs))

      | C_array { arr_flat=flat ; arr_element=t } ->
        let size = match flat with
          | None -> unsized_array ()
          | Some { arr_size } -> arr_size
        in
        let v = Lang.freshvar ~basename:"i" Lang.t_int in
        let obj = Ctypes.object_of t in
        let ofs = ofs @ [ Shift(obj, e_var v) ] in
        let low = Some (e_int 0) in
        let up = Some (e_int (size-1)) in
        let hyp = Vset.in_range (e_var v) low up in
        let in_range = monotonic_initialized seq obj x ofs in
        Lang.F.p_forall [v] (p_imply hyp in_range)

  let assigned_loc seq obj = function
    | Ref x -> noref ~op:"assigns to" x
    | Val((CVAL|CREF),x,[]) ->
      [ Assert (monotonic_initialized seq obj x []) ]
    | Val((CVAL|CREF),x,ofs) ->
      let value = Lang.freshvar ~basename:"v" (tau_of_object obj) in
      memvar_stored KValue seq x ofs (e_var value) ::
      if Cil.isStructOrUnionType x.vtype then []
      else begin
        let init = Lang.freshvar ~basename:"v" (init_of_object obj) in
        Assert(monotonic_initialized seq obj x ofs) ::
        [ memvar_stored KInit seq x ofs (e_var init) ]
      end
    | Val((HEAP|CTXT _|CARR _) as m,x,ofs) ->
      M.assigned (mseq_of_seq seq) obj (Sloc (mloc_of_path m x ofs))
    | Loc l ->
      M.assigned (mseq_of_seq seq) obj (Sloc l)

  let assigned_array seq obj l elt n =
    match l with
    | Ref x -> noref ~op:"assigns to" x
    | Val((CVAL|CREF),x,[]) ->
      if Ctypes.is_compound elt then []
      else
        (* Note that 'obj' above corresponds to the elements *)
        let obj = Ctypes.object_of x.vtype in
        [ Assert (monotonic_initialized seq obj x []) ]

    | Val((CVAL|CREF),x,ofs) ->
      let f_array t =
        e_var (Lang.freshvar ~basename:"v" Qed.Logic.(Array(Int, t))) in
      [ memvar_stored KValue seq x ofs (f_array @@ Lang.tau_of_object elt) ;
        memvar_stored KInit  seq x ofs (f_array @@ Lang.init_of_object elt) ]

    | Val((HEAP|CTXT _|CARR _) as m,x,ofs) ->
      let l = mloc_of_path m x ofs in
      M.assigned (mseq_of_seq seq) obj (Sarray(l,elt,n))
    | Loc l ->
      M.assigned (mseq_of_seq seq) obj (Sarray(l,elt,n))

  let assigned_range seq obj l elt a b =
    match l with
    | Ref x -> noref ~op:"assigns to" x
    | Loc l ->
      M.assigned (mseq_of_seq seq) obj (Srange(l,elt,a,b))
    | Val((HEAP|CTXT _|CARR _) as m,x,ofs) ->
      M.assigned (mseq_of_seq seq) obj (Srange(mloc_of_path m x ofs,elt,a,b))
    | Val((CVAL|CREF) as m,x,ofs) ->
      let k = Lang.freshvar ~basename:"k" Qed.Logic.Int in
      let p = Vset.in_range (e_var k) a b in
      let ofs = ofs_shift elt (e_var k) ofs in
      (* (monotonic_initialized_genset seq [k] m x ofs p) @*)
      (assigned_genset seq [k] m x ofs p)

  let assigned_descr seq obj xs l p =
    match l with
    | Ref x -> noref ~op:"assigns to" x
    | Loc l ->
      M.assigned (mseq_of_seq seq) obj (Sdescr(xs,l,p))
    | Val((HEAP|CTXT _|CARR _) as m,x,ofs) ->
      M.assigned (mseq_of_seq seq) obj (Sdescr(xs,mloc_of_path m x ofs,p))
    | Val((CVAL|CREF) as m,x,ofs) ->
      (* (monotonic_initialized_genset seq xs m x ofs p) @ *)
      (assigned_genset seq xs m x ofs p)

  let assigned seq obj = function
    | Sloc l -> assigned_loc seq obj l
    | Sarray(l,elt,n) -> assigned_array seq obj l elt n
    | Srange(l,elt,a,b) -> assigned_range seq obj l elt a b
    | Sdescr(xs,l,p) -> assigned_descr seq obj xs l p

  (* -------------------------------------------------------------------------- *)
  (* --- Segments                                                           --- *)
  (* -------------------------------------------------------------------------- *)

  type seq =
    | Rseg of varinfo
    | Fseg of varinfo * delta list
    | Mseg of M.loc rloc * varinfo * delta list
    | Lseg of M.loc rloc
  and delta =
    | Dfield of fieldinfo
    | Drange of term option * term option

  let dofs = function
    | Field f -> Dfield f
    | Shift(_,k) -> let u = Some k in Drange(u,u)

  let tofs = function
    | Field d -> Ctypes.object_of d.ftype
    | Shift(elt,_) -> elt

  let rec dstartof dim = function
    | C_array arr ->
      let n = match arr.arr_flat with None -> 1 | Some a -> a.arr_dim in
      if n > dim then
        let u = Some e_zero in
        let elt = Ctypes.object_of arr.arr_element in
        Drange(u,u) :: dstartof dim elt
      else []
    | _ -> []

  let rec doffset obj host = function
    | d::ds -> dofs d :: (doffset obj (tofs d) ds)
    | [] -> dstartof (Ctypes.get_array_dim obj) host

  let delta obj x ofs = doffset obj (Ctypes.object_of x.vtype) ofs

  let rec range ofs obj a b =
    match ofs with
    | [] -> [ Drange(a,b) ]
    | [Shift(elt,k)] when Ctypes.equal elt obj ->
      [ Drange( Vset.bound_shift a k , Vset.bound_shift b k ) ]
    | d :: ofs -> dofs d :: range ofs obj a b

  let locseg = function

    | Rloc(_,Ref x) -> Rseg x
    | Rrange(Ref x,_,_,_) -> noref ~op:"sub-range of" x

    | Rloc(obj,Loc l) -> Lseg (Rloc(obj,l))
    | Rloc(obj,Val((CVAL|CREF),x,ofs)) ->
      Fseg(x,delta obj x ofs)

    | Rrange(Loc l,obj,a,b) -> Lseg (Rrange(l,obj,a,b))
    | Rrange(Val((CVAL|CREF),x,ofs),obj,a,b) ->
      Fseg(x,range ofs obj a b)

    (* Nullable: force M without symbolic access *)
    | Rloc(obj,Val((CTXT Nullable|CARR Nullable) as m,x,ofs)) ->
      Lseg(Rloc(obj, mloc_of_path m x ofs))
    | Rrange(Val((CTXT Nullable|CARR Nullable) as m,x,ofs),obj,a,b) ->
      Lseg(Rrange(mloc_of_path m x ofs, obj, a, b))

    (* Otherwise: use M with symbolic access *)
    | Rloc(obj,Val((CTXT Valid|CARR Valid|HEAP) as m,x,ofs)) ->
      Mseg(Rloc(obj,mloc_of_path m x ofs),x,delta obj x ofs)
    | Rrange(Val((CTXT Valid|CARR Valid|HEAP) as m,x,ofs),obj,a,b) ->
      Mseg(Rrange(mloc_of_path m x ofs,obj,a,b),x,range ofs obj a b)

  (* -------------------------------------------------------------------------- *)
  (* ---  Segment Inclusion                                                 --- *)
  (* -------------------------------------------------------------------------- *)

  let rec included_delta d1 d2 =
    match d1 , d2 with
    | _ , [] -> p_true
    | [] , _ -> p_false
    | u :: d1 , v :: d2 ->
      match u , v with
      | Dfield f , Dfield g when Fieldinfo.equal f g ->
        included_delta d1 d2
      | Dfield _ , _ | _ , Dfield _ -> p_false
      | Drange(a1,b1) , Drange(a2,b2) ->
        p_conj [ Vset.ordered ~strict:false ~limit:true a2 a1 ;
                 Vset.ordered ~strict:false ~limit:true b1 b2 ;
                 included_delta d1 d2 ]

  let included s1 s2 =
    match locseg s1 , locseg s2 with
    | Rseg x , Rseg y -> if Varinfo.equal x y then p_true else p_false
    | Rseg _ , _ | _ , Rseg _ -> p_false

    | Fseg(x1,d1) , Fseg(x2,d2)
    | Mseg(_,x1,d1) , Mseg(_,x2,d2) ->
      if Varinfo.equal x1 x2 then included_delta d1 d2 else p_false

    | Fseg _ , _ | _ , Fseg _ -> p_false

    | (Lseg s1|Mseg(s1,_,_)) , (Lseg s2|Mseg(s2,_,_)) -> M.included s1 s2

  (* -------------------------------------------------------------------------- *)
  (* ---  Segment Separation                                                --- *)
  (* -------------------------------------------------------------------------- *)

  let rec separated_delta d1 d2 =
    match d1 , d2 with
    | [] , _ | _ , [] -> p_false
    | u :: d1 , v :: d2 ->
      match u , v with
      | Dfield f , Dfield g when Fieldinfo.equal f g
        -> separated_delta d1 d2
      | Dfield _ , _ | _ , Dfield _ -> p_true
      | Drange(a1,b1) , Drange(a2,b2) ->
        p_disj [ Vset.ordered ~strict:true ~limit:false b1 a2 ;
                 Vset.ordered ~strict:true ~limit:false b2 a1 ;
                 separated_delta d1 d2 ]

  let separated r1 r2 =
    match locseg r1 , locseg r2 with
    | Rseg x , Rseg y -> if Varinfo.equal x y then p_false else p_true
    | Rseg _ , _ | _ , Rseg _ -> p_true

    | Fseg(x1,d1) , Fseg(x2,d2)
    | Mseg(_,x1,d1) , Mseg(_,x2,d2) ->
      if Varinfo.equal x1 x2 then separated_delta d1 d2 else p_true
    | Fseg _ , _ | _ , Fseg _ -> p_true

    | (Lseg s1|Mseg(s1,_,_)) , (Lseg s2|Mseg(s2,_,_)) -> M.separated s1 s2

  (* -------------------------------------------------------------------------- *)
  (* ---  Domain                                                            --- *)
  (* -------------------------------------------------------------------------- *)

  let domain obj l =
    match l with
    | Ref x | Val((CVAL|CREF),x,_) ->
      let init =
        if not @@ Cvalues.always_initialized x then [ Init x ] else []
      in
      Heap.Set.of_list ((Var x) :: init)
    | Loc _ | Val((CTXT _|CARR _|HEAP),_,_) ->
      M.Heap.Set.fold
        (fun m s -> Heap.Set.add (Mem m) s)
        (M.domain obj (mloc_of_loc l)) Heap.Set.empty

  let is_well_formed sigma =
    let cstrs = ref [] in
    SIGMA.iter
      (fun v c -> cstrs := Cvalues.has_ctype v.vtype (e_var c) :: !cstrs)
      sigma.vars ;
    p_conj ((M.is_well_formed sigma.mem) :: !cstrs)

  (* -------------------------------------------------------------------------- *)

end