(*
 * Copyright (c) 2016 - present
 *
 * Programming Research Laboratory (ROPAS)
 * Seoul National University, Korea
 * All rights reserved.
 *
 * This source code is licensed under the BSD style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *)

open! IStd
open AbsLoc
module F = Format
module L = Logging
module Domain = BufferOverrunDomain
module Trace = BufferOverrunTrace
module TraceSet = Trace.Set
open Domain

module Make (CFG : ProcCfg.S) = struct
  exception Not_implemented

  let eval_const : Const.t -> Val.t = function
    | Const.Cint intlit -> (
      try Val.of_int (IntLit.to_int intlit)
      with _ -> Val.Itv.top )
    | Const.Cfloat f
     -> f |> int_of_float |> Val.of_int
    | _
     -> Val.Itv.top

  (* TODO *)

  let sizeof_ikind : Typ.ikind -> int = function
    | Typ.IChar | Typ.ISChar | Typ.IUChar | Typ.IBool
     -> 1
    | Typ.IInt | Typ.IUInt
     -> 4
    | Typ.IShort | Typ.IUShort
     -> 2
    | Typ.ILong | Typ.IULong
     -> 4
    | Typ.ILongLong | Typ.IULongLong
     -> 8
    | Typ.I128 | Typ.IU128
     -> 16

  let sizeof_fkind : Typ.fkind -> int = function
    | Typ.FFloat
     -> 4
    | Typ.FDouble | Typ.FLongDouble
     -> 8

  (* NOTE: assume 32bit machine *)
  let rec sizeof (typ: Typ.t) : int =
    match typ.desc with
    | Typ.Tint ikind
     -> sizeof_ikind ikind
    | Typ.Tfloat fkind
     -> sizeof_fkind fkind
    | Typ.Tvoid
     -> 1
    | Typ.Tptr (_, _)
     -> 4
    | Typ.Tstruct _ | Typ.TVar _
     -> 4 (* TODO *)
    | Typ.Tarray (_, Some length, Some stride)
     -> IntLit.to_int stride * IntLit.to_int length
    | Typ.Tarray (typ, Some length, None)
     -> sizeof typ * IntLit.to_int length
    | _
     -> 4

  let rec must_alias : Exp.t -> Exp.t -> Mem.astate -> bool =
    fun e1 e2 m ->
      match (e1, e2) with
      | Exp.Var x1, Exp.Var x2 -> (
        match (Mem.find_alias x1 m, Mem.find_alias x2 m) with
        | Some x1', Some x2'
         -> Loc.equal x1' x2'
        | _, _
         -> false )
      | Exp.UnOp (uop1, e1', _), Exp.UnOp (uop2, e2', _)
       -> Unop.equal uop1 uop2 && must_alias e1' e2' m
      | Exp.BinOp (bop1, e11, e12), Exp.BinOp (bop2, e21, e22)
       -> Binop.equal bop1 bop2 && must_alias e11 e21 m && must_alias e12 e22 m
      | Exp.Exn t1, Exp.Exn t2
       -> must_alias t1 t2 m
      | Exp.Const c1, Exp.Const c2
       -> Const.equal c1 c2
      | Exp.Cast (t1, e1'), Exp.Cast (t2, e2')
       -> Typ.equal t1 t2 && must_alias e1' e2' m
      | Exp.Lvar x1, Exp.Lvar x2
       -> Pvar.equal x1 x2
      | Exp.Lfield (e1, fld1, _), Exp.Lfield (e2, fld2, _)
       -> must_alias e1 e2 m && Typ.Fieldname.equal fld1 fld2
      | Exp.Lindex (e11, e12), Exp.Lindex (e21, e22)
       -> must_alias e11 e21 m && must_alias e12 e22 m
      | Exp.Sizeof {nbytes= Some nbytes1}, Exp.Sizeof {nbytes= Some nbytes2}
       -> Int.equal nbytes1 nbytes2
      | ( Exp.Sizeof {typ= t1; dynamic_length= dynlen1; subtype= subt1}
        , Exp.Sizeof {typ= t2; dynamic_length= dynlen2; subtype= subt2} )
       -> Typ.equal t1 t2 && must_alias_opt dynlen1 dynlen2 m
          && Int.equal (Subtype.compare subt1 subt2) 0
      | _, _
       -> false

  and must_alias_opt : Exp.t option -> Exp.t option -> Mem.astate -> bool =
    fun e1_opt e2_opt m ->
      match (e1_opt, e2_opt) with
      | Some e1, Some e2
       -> must_alias e1 e2 m
      | None, None
       -> true
      | _, _
       -> false

  let comp_rev : Binop.t -> Binop.t = function
    | Binop.Lt
     -> Binop.Gt
    | Binop.Gt
     -> Binop.Lt
    | Binop.Le
     -> Binop.Ge
    | Binop.Ge
     -> Binop.Le
    | Binop.Eq
     -> Binop.Eq
    | Binop.Ne
     -> Binop.Ne
    | _
     -> assert false

  let comp_not : Binop.t -> Binop.t = function
    | Binop.Lt
     -> Binop.Ge
    | Binop.Gt
     -> Binop.Le
    | Binop.Le
     -> Binop.Gt
    | Binop.Ge
     -> Binop.Lt
    | Binop.Eq
     -> Binop.Ne
    | Binop.Ne
     -> Binop.Eq
    | _
     -> assert false

  let rec must_alias_cmp : Exp.t -> Mem.astate -> bool =
    fun e m ->
      match e with
      | Exp.BinOp (Binop.Lt, e1, e2) | Exp.BinOp (Binop.Gt, e1, e2) | Exp.BinOp (Binop.Ne, e1, e2)
       -> must_alias e1 e2 m
      | Exp.BinOp (Binop.LAnd, e1, e2)
       -> must_alias_cmp e1 m || must_alias_cmp e2 m
      | Exp.BinOp (Binop.LOr, e1, e2)
       -> must_alias_cmp e1 m && must_alias_cmp e2 m
      | Exp.UnOp (Unop.LNot, Exp.UnOp (Unop.LNot, e1, _), _)
       -> must_alias_cmp e1 m
      | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Lt as c), e1, e2), _)
      | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Gt as c), e1, e2), _)
      | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Le as c), e1, e2), _)
      | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Ge as c), e1, e2), _)
      | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Eq as c), e1, e2), _)
      | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Ne as c), e1, e2), _)
       -> must_alias_cmp (Exp.BinOp (comp_not c, e1, e2)) m
      | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.LOr, e1, e2), t)
       -> let e1' = Exp.UnOp (Unop.LNot, e1, t) in
          let e2' = Exp.UnOp (Unop.LNot, e2, t) in
          must_alias_cmp (Exp.BinOp (Binop.LAnd, e1', e2')) m
      | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.LAnd, e1, e2), t)
       -> let e1' = Exp.UnOp (Unop.LNot, e1, t) in
          let e2' = Exp.UnOp (Unop.LNot, e2, t) in
          must_alias_cmp (Exp.BinOp (Binop.LOr, e1', e2')) m
      | _
       -> false

  let rec eval : Exp.t -> Mem.astate -> Location.t -> Val.t =
    fun exp mem loc ->
      if must_alias_cmp exp mem then Val.of_int 0
      else
        match exp with
        | Exp.Var id
         -> Mem.find_stack (Var.of_id id |> Loc.of_var) mem
        | Exp.Lvar pvar
         -> let ploc = pvar |> Loc.of_pvar |> PowLoc.singleton in
            let arr = Mem.find_stack_set ploc mem in
            ploc |> Val.of_pow_loc |> Val.join arr
        | Exp.UnOp (uop, e, _)
         -> eval_unop uop e mem loc
        | Exp.BinOp (bop, e1, e2)
         -> eval_binop bop e1 e2 mem loc
        | Exp.Const c
         -> eval_const c
        | Exp.Cast (_, e)
         -> eval e mem loc
        | Exp.Lfield (e, fn, _)
         -> eval e mem loc |> Val.get_array_locs |> Fn.flip PowLoc.append_field fn
            |> Val.of_pow_loc
        | Exp.Lindex (e1, _)
         -> let arr = eval e1 mem loc |> Val.get_array_blk in
            (* must have array blk *)
            (* let idx = eval e2 mem loc in *)
            let ploc = if ArrayBlk.is_bot arr then PowLoc.unknown else ArrayBlk.get_pow_loc arr in
            (* if nested array, add the array blk *)
            let arr = Mem.find_heap_set ploc mem in
            Val.join (Val.of_pow_loc ploc) arr
        | Exp.Sizeof {nbytes= Some size}
         -> Val.of_int size
        | Exp.Sizeof {typ; nbytes= None}
         -> Val.of_int (sizeof typ)
        | Exp.Exn _ | Exp.Closure _
         -> Val.Itv.top

  and eval_unop : Unop.t -> Exp.t -> Mem.astate -> Location.t -> Val.t =
    fun unop e mem loc ->
      let v = eval e mem loc in
      match unop with
      | Unop.Neg
       -> Val.neg v
      | Unop.BNot
       -> Val.unknown_bit v
      | Unop.LNot
       -> Val.lnot v

  and eval_binop : Binop.t -> Exp.t -> Exp.t -> Mem.astate -> Location.t -> Val.t =
    fun binop e1 e2 mem loc ->
      let v1 = eval e1 mem loc in
      let v2 = eval e2 mem loc in
      match binop with
      | Binop.PlusA
       -> Val.join (Val.plus v1 v2) (Val.plus_pi v1 v2)
      | Binop.PlusPI
       -> Val.plus_pi v1 v2
      | Binop.MinusA
       -> Val.joins [Val.minus v1 v2; Val.minus_pi v1 v2; Val.minus_pp v1 v2]
      | Binop.MinusPI
       -> Val.minus_pi v1 v2
      | Binop.MinusPP
       -> Val.minus_pp v1 v2
      | Binop.Mult
       -> Val.mult v1 v2
      | Binop.Div
       -> Val.div v1 v2
      | Binop.Mod
       -> Val.mod_sem v1 v2
      | Binop.Shiftlt
       -> Val.shiftlt v1 v2
      | Binop.Shiftrt
       -> Val.shiftrt v1 v2
      | Binop.Lt
       -> Val.lt_sem v1 v2
      | Binop.Gt
       -> Val.gt_sem v1 v2
      | Binop.Le
       -> Val.le_sem v1 v2
      | Binop.Ge
       -> Val.ge_sem v1 v2
      | Binop.Eq
       -> Val.eq_sem v1 v2
      | Binop.Ne
       -> Val.ne_sem v1 v2
      | Binop.BAnd | Binop.BXor | Binop.BOr
       -> Val.unknown_bit v1
      | Binop.LAnd
       -> Val.land_sem v1 v2
      | Binop.LOr
       -> Val.lor_sem v1 v2

  let rec eval_locs : Exp.t -> Mem.astate -> Location.t -> Val.t =
    fun exp mem loc ->
      match exp with
      | Exp.Var id -> (
        match Mem.find_alias id mem with
        | Some loc
         -> PowLoc.singleton loc |> Val.of_pow_loc
        | None
         -> Val.bot )
      | Exp.Lvar pvar
       -> pvar |> Loc.of_pvar |> PowLoc.singleton |> Val.of_pow_loc
      | Exp.BinOp (bop, e1, e2)
       -> eval_binop bop e1 e2 mem loc
      | Exp.Cast (_, e)
       -> eval_locs e mem loc
      | Exp.Lfield (e, fn, _)
       -> eval e mem loc |> Val.get_all_locs |> Fn.flip PowLoc.append_field fn |> Val.of_pow_loc
      | Exp.Lindex (e1, e2)
       -> let arr = eval e1 mem loc in
          let idx = eval e2 mem loc in
          Val.plus_pi arr idx
      | Exp.Const _ | Exp.UnOp _ | Exp.Sizeof _ | Exp.Exn _ | Exp.Closure _
       -> Val.bot

  let get_allocsite : Typ.Procname.t -> CFG.node -> int -> int -> string =
    fun proc_name node inst_num dimension ->
      let proc_name = Typ.Procname.to_string proc_name in
      let node_num = CFG.hash node |> string_of_int in
      let inst_num = string_of_int inst_num in
      let dimension = string_of_int dimension in
      proc_name ^ "-" ^ node_num ^ "-" ^ inst_num ^ "-" ^ dimension |> Allocsite.make

  let eval_array_alloc
      : Typ.Procname.t -> CFG.node -> Typ.t -> ?stride:int -> Itv.t -> Itv.t -> int -> int -> Val.t =
    fun pdesc node typ ?stride:stride0 offset size inst_num dimension ->
      let allocsite = get_allocsite pdesc node inst_num dimension in
      let int_stride = match stride0 with None -> sizeof typ | Some stride -> stride in
      let stride = Itv.of_int int_stride in
      ArrayBlk.make allocsite offset size stride |> Val.of_array_blk

  let prune_unop : Exp.t -> Mem.astate -> Mem.astate =
    fun e mem ->
      match e with
      | Exp.Var x -> (
        match Mem.find_alias x mem with
        | Some lv
         -> let v = Mem.find_heap lv mem in
            let v' = Val.prune_zero v in
            Mem.update_mem (PowLoc.singleton lv) v' mem
        | None
         -> mem )
      | Exp.UnOp (Unop.LNot, Exp.Var x, _) -> (
        match Mem.find_alias x mem with
        | Some lv
         -> let v = Mem.find_heap lv mem in
            let itv_v = Itv.prune_ne (Val.get_itv v) Itv.false_sem in
            let v' = Val.modify_itv itv_v v in
            Mem.update_mem (PowLoc.singleton lv) v' mem
        | None
         -> mem )
      | _
       -> mem

  let prune_binop_left : Exp.t -> Location.t -> Mem.astate -> Mem.astate =
    fun e loc mem ->
      match e with
      | Exp.BinOp ((Binop.Lt as comp), Exp.Var x, e')
      | Exp.BinOp ((Binop.Gt as comp), Exp.Var x, e')
      | Exp.BinOp ((Binop.Le as comp), Exp.Var x, e')
      | Exp.BinOp ((Binop.Ge as comp), Exp.Var x, e') -> (
        match Mem.find_alias x mem with
        | Some lv
         -> let v = Mem.find_heap lv mem in
            let v' = Val.prune_comp comp v (eval e' mem loc) in
            Mem.update_mem (PowLoc.singleton lv) v' mem
        | None
         -> mem )
      | Exp.BinOp (Binop.Eq, Exp.Var x, e') -> (
        match Mem.find_alias x mem with
        | Some lv
         -> let v = Mem.find_heap lv mem in
            let v' = Val.prune_eq v (eval e' mem loc) in
            Mem.update_mem (PowLoc.singleton lv) v' mem
        | None
         -> mem )
      | Exp.BinOp (Binop.Ne, Exp.Var x, e') -> (
        match Mem.find_alias x mem with
        | Some lv
         -> let v = Mem.find_heap lv mem in
            let v' = Val.prune_ne v (eval e' mem loc) in
            Mem.update_mem (PowLoc.singleton lv) v' mem
        | None
         -> mem )
      | _
       -> mem

  let prune_binop_right : Exp.t -> Location.t -> Mem.astate -> Mem.astate =
    fun e loc mem ->
      match e with
      | Exp.BinOp ((Binop.Lt as c), e', Exp.Var x)
      | Exp.BinOp ((Binop.Gt as c), e', Exp.Var x)
      | Exp.BinOp ((Binop.Le as c), e', Exp.Var x)
      | Exp.BinOp ((Binop.Ge as c), e', Exp.Var x)
      | Exp.BinOp ((Binop.Eq as c), e', Exp.Var x)
      | Exp.BinOp ((Binop.Ne as c), e', Exp.Var x)
       -> prune_binop_left (Exp.BinOp (comp_rev c, Exp.Var x, e')) loc mem
      | _
       -> mem

  let is_unreachable_constant : Exp.t -> Location.t -> Mem.astate -> bool =
    fun e loc m -> Val.( <= ) ~lhs:(eval e m loc) ~rhs:(Val.of_int 0)

  let prune_unreachable : Exp.t -> Location.t -> Mem.astate -> Mem.astate =
    fun e loc mem -> if is_unreachable_constant e loc mem then Mem.bot else mem

  let rec prune : Exp.t -> Location.t -> Mem.astate -> Mem.astate =
    fun e loc mem ->
      let mem =
        mem |> prune_unreachable e loc |> prune_unop e |> prune_binop_left e loc
        |> prune_binop_right e loc
      in
      match e with
      | Exp.BinOp (Binop.Ne, e, Exp.Const Const.Cint i) when IntLit.iszero i
       -> prune e loc mem
      | Exp.BinOp (Binop.Eq, e, Exp.Const Const.Cint i) when IntLit.iszero i
       -> prune (Exp.UnOp (Unop.LNot, e, None)) loc mem
      | Exp.UnOp (Unop.Neg, Exp.Var x, _)
       -> prune (Exp.Var x) loc mem
      | Exp.BinOp (Binop.LAnd, e1, e2)
       -> mem |> prune e1 loc |> prune e2 loc
      | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.LOr, e1, e2), t)
       -> mem |> prune (Exp.UnOp (Unop.LNot, e1, t)) loc |> prune (Exp.UnOp (Unop.LNot, e2, t)) loc
      | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Lt as c), e1, e2), _)
      | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Gt as c), e1, e2), _)
      | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Le as c), e1, e2), _)
      | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Ge as c), e1, e2), _)
      | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Eq as c), e1, e2), _)
      | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Ne as c), e1, e2), _)
       -> prune (Exp.BinOp (comp_not c, e1, e2)) loc mem
      | _
       -> mem

  let get_formals : Procdesc.t -> (Pvar.t * Typ.t) list =
    fun pdesc ->
      let proc_name = Procdesc.get_proc_name pdesc in
      Procdesc.get_formals pdesc |> List.map ~f:(fun (name, typ) -> (Pvar.mk name proc_name, typ))

  let get_matching_pairs
      : Tenv.t -> Val.t -> Val.t -> Typ.t -> Mem.astate -> Mem.astate
        -> callee_ret_alias:Loc.t option
        -> (Itv.Bound.t * Itv.Bound.t * TraceSet.t) list * Loc.t option =
    fun tenv formal actual typ caller_mem callee_mem ~callee_ret_alias ->
      let get_itv v = Val.get_itv v in
      let get_offset v = v |> Val.get_array_blk |> ArrayBlk.offsetof in
      let get_size v = v |> Val.get_array_blk |> ArrayBlk.sizeof in
      let get_field_name (fn, _, _) = fn in
      let append_field v fn = PowLoc.append_field (Val.get_all_locs v) fn in
      let deref_field v fn mem = Mem.find_heap_set (append_field v fn) mem in
      let deref_ptr v mem = Mem.find_heap_set (Val.get_array_locs v) mem in
      let ret_alias = ref None in
      let add_ret_alias v1 v2 =
        match callee_ret_alias with
        | Some ret_loc
         -> if PowLoc.is_singleton v1 && PowLoc.is_singleton v2
               && Loc.equal (PowLoc.choose v1) ret_loc
            then ret_alias := Some (PowLoc.choose v2)
        | None
         -> ()
      in
      let add_pair_itv itv1 itv2 traces l =
        let open Itv in
        if itv1 <> bot && itv1 <> top && itv2 <> bot then (lb itv1, lb itv2, traces)
          :: (ub itv1, ub itv2, traces) :: l
        else if itv1 <> bot && itv1 <> top && Itv.eq itv2 bot then
          (lb itv1, Bound.Bot, TraceSet.empty) :: (ub itv1, Bound.Bot, TraceSet.empty) :: l
        else l
      in
      let add_pair_val v1 v2 pairs =
        add_ret_alias (Val.get_all_locs v1) (Val.get_all_locs v2) ;
        pairs |> add_pair_itv (get_itv v1) (get_itv v2) (Val.get_traces v2)
        |> add_pair_itv (get_offset v1) (get_offset v2) (Val.get_traces v2)
        |> add_pair_itv (get_size v1) (get_size v2) (Val.get_traces v2)
      in
      let add_pair_field v1 v2 pairs fn =
        add_ret_alias (append_field v1 fn) (append_field v2 fn) ;
        let v1' = deref_field v1 fn callee_mem in
        let v2' = deref_field v2 fn caller_mem in
        add_pair_val v1' v2' pairs
      in
      let add_pair_ptr typ v1 v2 pairs =
        add_ret_alias (Val.get_all_locs v1) (Val.get_all_locs v2) ;
        match typ.Typ.desc with
        | Typ.Tptr ({desc= Tstruct typename}, _) -> (
          match Tenv.lookup tenv typename with
          | Some str
           -> let fns = List.map ~f:get_field_name str.Typ.Struct.fields in
              List.fold ~f:(add_pair_field v1 v2) ~init:pairs fns
          | _
           -> pairs )
        | Typ.Tptr (_, _)
         -> let v1' = deref_ptr v1 callee_mem in
            let v2' = deref_ptr v2 caller_mem in
            add_pair_val v1' v2' pairs
        | _
         -> pairs
      in
      let pairs = [] |> add_pair_val formal actual |> add_pair_ptr typ formal actual in
      (pairs, !ret_alias)

  let subst_map_of_pairs
      : (Itv.Bound.t * Itv.Bound.t * TraceSet.t) list
        -> Itv.Bound.t Itv.SubstMap.t * TraceSet.t Itv.SubstMap.t =
    fun pairs ->
      let add_pair (bound_map, trace_map) (formal, actual, traces) =
        match formal with
        | Itv.Bound.Linear (_, se1) when Itv.SymLinear.is_zero se1
         -> (bound_map, trace_map)
        | Itv.Bound.Linear (0, se1) when Itv.SymLinear.cardinal se1 > 0
         -> let symbol, coeff = Itv.SymLinear.choose se1 in
            if Int.equal coeff 1 then
              (Itv.SubstMap.add symbol actual bound_map, Itv.SubstMap.add symbol traces trace_map)
            else assert false
        | Itv.Bound.MinMax (Itv.Bound.Max, 0, symbol)
         -> (Itv.SubstMap.add symbol actual bound_map, Itv.SubstMap.add symbol traces trace_map)
        | _
         -> assert false
      in
      List.fold ~f:add_pair ~init:(Itv.SubstMap.empty, Itv.SubstMap.empty) pairs

  let rec list_fold2_def
      : default:Val.t -> f:('a -> Val.t -> 'b -> 'b) -> 'a list -> Val.t list -> init:'b -> 'b =
    fun ~default ~f xs ys ~init:acc ->
      match (xs, ys) with
      | [], _
       -> acc
      | x :: xs', []
       -> list_fold2_def ~default ~f xs' ys ~init:(f x default acc)
      | [x], _ :: _
       -> f x (List.fold ~f:Val.join ~init:Val.bot ys) acc
      | x :: xs', y :: ys'
       -> list_fold2_def ~default ~f xs' ys' ~init:(f x y acc)

  let get_subst_map
      : Tenv.t -> Procdesc.t -> (Exp.t * 'a) list -> Mem.astate -> Mem.astate
        -> callee_ret_alias:Loc.t option -> Location.t
        -> (Itv.Bound.t Itv.SubstMap.t * TraceSet.t Itv.SubstMap.t) * Loc.t option =
    fun tenv callee_pdesc params caller_mem callee_entry_mem ~callee_ret_alias loc ->
      let add_pair (formal, typ) actual (l, ret_alias) =
        let formal = Mem.find_heap (Loc.of_pvar formal) callee_entry_mem in
        let new_matching, ret_alias' =
          get_matching_pairs tenv formal actual typ caller_mem callee_entry_mem ~callee_ret_alias
        in
        (List.rev_append new_matching l, Option.first_some ret_alias ret_alias')
      in
      let formals = get_formals callee_pdesc in
      let actuals = List.map ~f:(fun (a, _) -> eval a caller_mem loc) params in
      let pairs, ret_alias =
        list_fold2_def ~default:Val.Itv.top ~f:add_pair formals actuals ~init:([], None)
      in
      (subst_map_of_pairs pairs, ret_alias)
end
