(**************************************************************************)
(*                                                                        *)
(*  This file is part of Frama-C.                                         *)
(*                                                                        *)
(*  Copyright (C) 2007-2025                                               *)
(*    CEA (Commissariat à l'énergie atomique et aux énergies              *)
(*         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).            *)
(*                                                                        *)
(**************************************************************************)

open Cil_types
open Locations
open Cvalue

(* Truth values for a predicate analyzed by the value analysis *)

type predicate_status = Abstract_interp.Comp.result = True | False | Unknown

let join_predicate_status x y = match x, y with
  | True, True -> True
  | False, False -> False
  | True, False | False, True
  | Unknown, _ | _, Unknown -> Unknown

(* Type of possible errors during evaluation. See pretty-printer for details *)
type logic_evaluation_error =
  | Unsupported of string
  | UnsupportedLogicVar of logic_var
  | AstError of string
  | NoEnv of logic_label
  | NoResult
  | CAlarm

let pretty_logic_evaluation_error fmt = function
  | Unsupported s -> Format.fprintf fmt "unsupported ACSL construct: %s" s
  | UnsupportedLogicVar tv ->
    Format.fprintf fmt "unsupported logic var %s" tv.lv_name
  | AstError s -> Format.fprintf fmt "error in AST: %s; please report" s
  | NoEnv (FormalLabel s) ->
    Format.fprintf fmt "no environment to evaluate \\at(_,%s)" s
  | NoEnv (BuiltinLabel l) ->
    Format.fprintf fmt "no environment to evaluate \\at(_,%a)"
      Printer.pp_logic_builtin_label l
  | NoEnv (StmtLabel _) ->
    Format.fprintf fmt "\\at() on a C label is unsupported"
  | NoResult -> Format.fprintf fmt "meaning of \\result not specified"
  | CAlarm -> Format.fprintf fmt "alarm during evaluation"

exception LogicEvalError of logic_evaluation_error

let unsupported s = raise (LogicEvalError (Unsupported s))
let unsupported_lvar v = raise (LogicEvalError (UnsupportedLogicVar v))
let ast_error s = raise (LogicEvalError (AstError s))
let no_env lbl = raise (LogicEvalError (NoEnv lbl))
let no_result () = raise (LogicEvalError NoResult)
let c_alarm () = raise (LogicEvalError CAlarm)

(** Three modes to handle the alarms when evaluating a logical term. *)
type alarm_mode =
  | Ignore            (* Ignores all alarms. *)
  | Fail              (* Raises a LogicEvalError when an alarm is encountered. *)
  | Track of bool ref (* Tracks the possibility of an alarm in the boolean. *)

(* Process the possibility of an alarm according to the alarm_mode.
   The boolean [b] is true when an alarm is possible. *)
let track_alarms b = function
  | Ignore -> ()
  | Fail -> if b then c_alarm ()
  | Track bref -> if b then bref := true

let display_evaluation_error ~loc = function
  | CAlarm -> ()
  | pa ->
    Self.result ~source:(fst loc) ~once:true
      "cannot evaluate ACSL term, %a" pretty_logic_evaluation_error pa

(* Warning mode use when performing _reductions_ in the logic ( ** not **
   evaluation). "Logic alarms" are ignored, and the reduction proceeds as if
   they had not occurred. *)
let alarm_reduce_mode () =
  if Parameters.ReduceOnLogicAlarms.get () then Ignore else Fail

let find_indeterminate ~alarm_mode state loc =
  let is_invalid = not Locations.(is_valid Read loc) in
  track_alarms is_invalid alarm_mode;
  Model.find_indeterminate ~conflate_bottom:true state loc

let find_or_alarm ~alarm_mode state loc =
  let v = find_indeterminate ~alarm_mode state loc in
  let is_indeterminate = Cvalue.V_Or_Uninitialized.is_indeterminate v in
  track_alarms is_indeterminate alarm_mode;
  V_Or_Uninitialized.get_v v

(* Returns true if [loc] contains a memory location definitely invalid
   for a memory access of kind [access]. *)
let contains_invalid_loc access loc =
  let valid_loc = Locations.valid_part access loc in
  not (Locations.Location.equal loc valid_loc)

(* -------------------------------------------------------------------------- *)
(* --- Evaluation environments.                                           --- *)
(* -------------------------------------------------------------------------- *)

(* Evaluation environments are used to evaluate predicate on \at nodes,
   the varinfo \result and logic variables introduced by quantifiers. *)

(* Labels:
   pre: pre-state of the function. Equivalent to \old in the postcondition
     (and displayed as such)
   here: current location, always the intuitive meaning. Assertions are
     evaluated before the statement.
   post: forbidden in preconditions;
     In postconditions:
      in function contracts, state of in the post-state
      in statement contracts, state after the evaluation of the statement
   old: forbidden in assertions.
        In statement contracts post, means the state before the statement
        In functions contracts post, means the pre-state
*)

(* TODO: evaluating correctly Pat with the current Value domain is tricky,
   and only works reliably for the four labels below, that are either
   invariant during the course of the program, or fully local. The
   program below shows the problem:
   if (c) x = 1; else x = 3;
   L:
   x = 1;
   \assert \at(x == 1, L);
   A naive implementation of assertions involving C labels is likely to miss
   the fact that the assertion is false after the else branch. A good
   solution is to use a dummy edge that flows from L to the assertion,
   to force its re-evaluation.
*)

module Logic_label = Cil_datatype.Logic_label
type labels_states = Cvalue.Model.t Logic_label.Map.t

let join_label_states m1 m2 =
  let aux _ s1 s2 = Some (Cvalue.Model.join s1 s2) in
  if m1 == m2 then m1
  else Logic_label.Map.union aux m1 m2

(* Environment for mathematical variables introduced by quantifiers:
   maps from their int identifiers to cvalues representing an over-approximation
   of their values (either mathematical integers or mathematical reals). *)
module LogicVarEnv = struct
  include Cil_datatype.Logic_var.Map

  let find logic_var map =
    try find logic_var map
    with Not_found -> unsupported_lvar logic_var

  let join m1 m2 =
    let aux _ v1 v2 = Some (Cvalue.V.join v1 v2) in
    if m1 == m2 then m1
    else union aux m1 m2
end

type logic_env = Cvalue.V.t LogicVarEnv.t

(* The logic can refer to the state at other points of the program
   using labels. [e_cur] indicates the current label (in changes when
   evaluating the term in a \at(label,term). [e_states] associates a
   memory state to each label. [logic_vars] binds mathematical variables
   to their possible values. [result] contains the variable
   corresponding to \result; this works even with leaf functions
   without a body. [result] is None when \result is meaningless
   (e.g. the function returns void, logic outside of a function
   contract, etc.) *)
type eval_env = {
  e_cur: logic_label;
  e_states: labels_states;
  logic_vars: logic_env;
  result: varinfo option;
}

let join_env e1 e2 = {
  e_cur = (assert (Logic_label.equal e1.e_cur e2.e_cur); e1.e_cur);
  e_states = join_label_states e1.e_states e2.e_states;
  logic_vars = LogicVarEnv.join e1.logic_vars e2.logic_vars;
  result = (assert (e1.result == e2.result); e1.result);
}

let env_state env lbl =
  try Logic_label.Map.find lbl env.e_states
  with Not_found -> no_env lbl

let env_current_state e = env_state e e.e_cur

let overwrite_state env state lbl =
  { env with e_states = Logic_label.Map.add lbl state env.e_states }

let overwrite_current_state env state = overwrite_state env state env.e_cur

let lbl_here = Logic_const.here_label

let add_logic ll state (states: labels_states): labels_states =
  Logic_label.Map.add ll state states
let add_here = add_logic Logic_const.here_label
let add_pre = add_logic Logic_const.pre_label
let add_post = add_logic Logic_const.post_label
let add_old = add_logic Logic_const.old_label
(* Init is a bit special, it is constant and always added to the initial state*)
let add_init states =
  match Cvalue_results.get_global_state () with
  | `Bottom -> states
  | `Value state -> add_logic Logic_const.init_label state states

let add_logic_var env lv cvalue =
  { env with logic_vars = LogicVarEnv.add lv cvalue env.logic_vars }

let make_env logic_env state =
  let transfer label map =
    Logic_label.Map.add label (logic_env.Abstract_domain.states label) map
  in
  let map =
    Logic_label.Map.add lbl_here state
      (transfer Logic_const.pre_label
         (transfer Logic_const.old_label
            (transfer Logic_const.post_label
               (add_init Logic_label.Map.empty))))
  in
  { e_cur = lbl_here;
    e_states = map;
    logic_vars = LogicVarEnv.empty;
    result = logic_env.Abstract_domain.result }

let env_pre_f ~pre () = {
  e_cur = lbl_here;
  e_states = add_here pre (add_pre pre (add_init Logic_label.Map.empty));
  logic_vars = LogicVarEnv.empty;
  result = None (* Never useful in a pre *);
}

let env_post_f ?(c_labels=Logic_label.Map.empty) ~pre ~post ~result () = {
  e_cur = lbl_here;
  e_states = add_post post
      (add_here post (add_pre pre (add_old pre (add_init c_labels))));
  logic_vars = LogicVarEnv.empty;
  result = result;
}

let env_annot ?(c_labels=Logic_label.Map.empty) ~pre ~here () = {
  e_cur = lbl_here;
  e_states = add_here here (add_pre pre (add_init c_labels));
  logic_vars = LogicVarEnv.empty;
  result = None (* Never useful in a 'assert'. TODO: will be needed for stmt
                   contracts *);
}

let env_assigns ~pre = {
  e_cur = lbl_here;
  (* YYY: Post label is missing, but is too difficult in the current evaluation
          scheme, since we build it by evaluating the assigns... *)
  e_states = add_old pre
      (add_here pre (add_pre pre (add_init Logic_label.Map.empty)));
  logic_vars = LogicVarEnv.empty;
  result = None (* Treated in a special way in callers *)
}

let env_only_here state = {
  e_cur = lbl_here;
  e_states = add_here state (add_init Logic_label.Map.empty);
  logic_vars = LogicVarEnv.empty;
  result = None (* Never useful in a 'assert'. TODO: will be needed for stmt
                   contracts *);
}

let top_float =
  let neg_infinity = Fval.F.of_float neg_infinity
  and pos_infinity = Fval.F.of_float infinity in
  let fval = Fval.inject ~nan:false Fval.Real neg_infinity pos_infinity in
  Ival.inject_float fval

let bind_logic_vars env lvs =
  let bind_one (state, logic_vars) lv =
    let bind_logic_var ival =
      state, LogicVarEnv.add lv (Cvalue.V.inject_ival ival) logic_vars
    in
    match Logic_utils.unroll_type lv.lv_type with
    | Linteger -> bind_logic_var Ival.top
    | Lreal -> bind_logic_var top_float
    | Ctype ctyp when Cil.isIntegralType ctyp ->
      let base = Base.of_c_logic_var lv in
      let size = Integer.of_int (Cil.bitsSizeOf ctyp) in
      let v = Cvalue.V_Or_Uninitialized.initialized V.top_int in
      let state = Model.add_base_value base ~size v ~size_v:Integer.one state in
      state, logic_vars
    | _ -> unsupported_lvar lv
  in
  let state = env_current_state env in
  let state, logic_vars = List.fold_left bind_one (state, env.logic_vars) lvs in
  overwrite_current_state { env with logic_vars } state

let copy_logic_vars ~src ~dst lvars =
  let copy_one env lvar =
    match Logic_utils.unroll_type lvar.lv_type with
    | Linteger | Lreal ->
      let value = LogicVarEnv.find lvar src.logic_vars in
      let logic_vars = LogicVarEnv.add lvar value env.logic_vars in
      { env with logic_vars }
    | Ctype _ ->
      begin
        let base = Base.of_c_logic_var lvar in
        match Model.find_base base (env_current_state src) with
        | `Bottom | `Top -> env
        | `Value offsm ->
          let state = Model.add_base base offsm (env_current_state env) in
          overwrite_current_state env state
      end
    | _ -> unsupported_lvar lvar
  in
  List.fold_left copy_one dst lvars

let unbind_logic_vars env lvs =
  let unbind_one (state, logic_vars) lv =
    match Logic_utils.unroll_type lv.lv_type with
    | Linteger | Lreal -> state, LogicVarEnv.remove lv logic_vars
    | Ctype _ ->
      let base = Base.of_c_logic_var lv in
      Model.remove_base base state, logic_vars
    | _ -> unsupported_lvar lv
  in
  let state = env_current_state env in
  let state, logic_vars = List.fold_left unbind_one (state, env.logic_vars) lvs in
  overwrite_current_state { env with logic_vars } state

(* -------------------------------------------------------------------------- *)
(* --- Evaluation results.                                                --- *)
(* -------------------------------------------------------------------------- *)

(* Result of the evaluation of a term as an exact location (to reduce its value
   in the current state). *)
type exact_location =
  | Location of typ * Locations.location
  (* A location represented in the cvalue state. *)
  | Logic_var of logic_var
  (* A logic variable, introduced by a quantifier, and stored in an environment
     besides the states. *)

(* Raised when a term cannot be evaluated as an exact location.*)
exception Not_an_exact_loc

type logic_deps = Zone.t Logic_label.Map.t

let deps_at lbl (ld:logic_deps) =
  try Logic_label.Map.find lbl ld
  with Not_found -> Zone.bottom

let add_deps lbl ldeps deps =
  let prev_deps = deps_at lbl ldeps in
  let deps = Zone.join prev_deps deps in
  let ldeps : logic_deps = Logic_label.Map.add lbl deps ldeps in
  ldeps

let join_logic_deps (ld1:logic_deps) (ld2: logic_deps) : logic_deps =
  let aux _ d1 d2 = match d1, d2 with
    | None as d, None | (Some _ as d), None | None, (Some _ as d) -> d
    | Some d1, Some d2 -> Some (Zone.join d1 d2)
  in
  Logic_label.Map.merge aux ld1 ld2

let empty_logic_deps =
  Logic_label.Map.add lbl_here Zone.bottom Logic_label.Map.empty

(* Type holding the result of an evaluation. Currently, 'a is either
   [Cvalue.V.t] for [eval_term], and [Location_Bits.t] for [eval_tlval_as_loc],
   and [Ival.t] for [eval_toffset].

   [eover] is an over-approximation of the evaluation. [eunder] is an
   under-approximation, under the hypothesis that the state in which we
   evaluate is not Bottom. (Otherwise, all under-approximations would be
   Bottom themselves).
   The following two invariants should hold:
   (1) eunder \subset eover.
   (2) when evaluating something that is not a Tset, either eunder = Bottom,
      or eunder = eover, and cardinal(eover) <= 1. This is due to the fact
      that under-approximations are not propagated as an abstract domain, but
      only created from Trange or inferred from exact over-approximations.

   This type can be used for the evaluation of logical sets, in which case
   an eval_result [r] represents all *non-empty* sets [S] such that:
   [r.eunder ⊆ S ⊆ r.eover]. The value {V.bottom} does *not* represents empty
   sets, as for most predicates, P(∅) ≠ P(⊥) (if the latter has a meaning).
   The boolean [empty] indicates whether an eval_result also represents a
   possible empty set. *)
type 'a eval_result = {
  etype: Cil_types.typ;
  eunder: 'a;
  eover: 'a;
  empty: bool;
  ldeps: logic_deps;
}

let join_eval_result r1 r2 =
  let eover = Cvalue.V.join r2.eover r1.eover in
  let eunder = Cvalue.V.meet r1.eunder r2.eunder in
  let ldeps = join_logic_deps r1.ldeps r2.ldeps in
  { eover; eunder; ldeps; etype = r1.etype; empty = r1.empty || r2.empty; }

(* When computing an under-approximation, we make the hypothesis that the state
   is not Bottom. Hence, over-approximations of cardinal <= 1 are actually of
   cardinal 1, and are thus exact. *)
let under_from_over eover =
  if Cvalue.V.cardinal_zero_or_one eover
  then eover
  else Cvalue.V.bottom

let under_loc_from_over eover =
  if Locations.Location_Bits.cardinal_zero_or_one eover
  then eover
  else Locations.Location_Bits.bottom

(* Injects [cvalue] as an eval_result of type [typ] with no dependencies. *)
let inject_no_deps typ cvalue =
  { etype = typ;
    eunder = under_from_over cvalue;
    eover = cvalue;
    empty = false;
    ldeps = empty_logic_deps }

(* Integer result with no memory dependencies: constants, sizeof & alignof,
   and values of logic variables.*)
let einteger = inject_no_deps Cil_const.intType

(* Note: some reals cannot be exactly represented as floats; in which
   case we do not know their under-approximation. *)
let ereal fval = inject_no_deps Cil_const.doubleType (Cvalue.V.inject_float fval)
let efloat fval = inject_no_deps Cil_const.floatType (Cvalue.V.inject_float fval)

(* -------------------------------------------------------------------------- *)
(* --- Utilitary functions on the Cil AST                                 --- *)
(* -------------------------------------------------------------------------- *)

let is_rel_binop = function
  | Lt
  | Gt
  | Le
  | Ge
  | Eq
  | Ne -> true
  | _ -> false

let lop_to_cop op =
  match op with
  | Req -> Eq
  | Rneq -> Ne
  | Rle -> Le
  | Rge -> Ge
  | Rlt -> Lt
  | Rgt -> Gt

let rel_of_binop = function
  | Lt -> Rlt
  | Gt -> Rgt
  | Le -> Rle
  | Ge -> Rge
  | Eq -> Req
  | Ne -> Rneq
  | _ -> assert false

(* Types currently understood in the evaluation of the logic: no arrays,
   structs, logic arrays or subtle ACSL types. Sets of sets seem to
   be flattened, so the current treatment of them is correct. *)
let rec isLogicNonCompositeType t =
  match t with
  | Lvar _ | Larrow _ -> false
  | Ltype (info, _) ->
    info.lt_name = "sign" ||
    (try isLogicNonCompositeType (Logic_const.type_of_element t)
     with Failure _ -> false)
  | Lboolean | Linteger | Lreal -> true
  | Ctype t -> Cil.isScalarType t

let rec infer_type = function
  | Ctype t ->
    (match t.tnode with
     | TInt _ -> Cil_const.intType
     | TFloat _ -> Cil_const.doubleType
     | _ -> t)
  | Lvar _ -> Cil_const.voidPtrType (* For polymorphic empty sets *)
  | Lboolean -> Cil_const.boolType
  | Linteger -> Cil_const.intType
  | Lreal -> Cil_const.doubleType
  | Ltype _ | Larrow _ as t ->
    if Logic_const.is_plain_type t then
      unsupported (Pretty_utils.to_string Cil_datatype.Logic_type.pretty t)
    else Logic_const.plain_or_set infer_type t

(* Best effort for comparing the types currently understood by Value: ignore
   differences in integer and floating-point sizes, that are meaningless
   in the logic *)
let same_etype t1 t2 =
  match Cil.unrollTypeNode t1, Cil.unrollTypeNode t2 with
  | (TInt _ | TEnum _), (TInt _ | TEnum _) -> true
  | TFloat _, TFloat _ -> true
  | TPtr p1, TPtr p2 -> Cil_datatype.Typ.equal p1 p2
  | _, _ -> Cil_datatype.Typ.equal t1 t2

(* Returns the kind of floating-point represented by a logic type, or None. *)
let logic_type_fkind = function
  | Ctype typ -> begin
      match Cil.unrollTypeNode typ with
      | TFloat fkind -> Some fkind
      | _ -> None
    end
  | _ -> None

let infer_binop_res_type op targ =
  match op with
  | PlusA | MinusA | Mult | Div -> targ
  | PlusPI | MinusPI ->
    assert (Cil.isPointerType targ); targ
  | MinusPP -> Cil_const.intType
  | Mod | Shiftlt | Shiftrt | BAnd | BXor | BOr ->
    (* can only be applied on integral arguments *)
    assert (Cil.isIntegralType targ); Cil_const.intType
  | Lt | Gt | Le | Ge | Eq | Ne | LAnd | LOr ->
    Cil_const.intType (* those operators always return a boolean *)

(* Computes [*tsets], assuming that [tsets] has a pointer type. *)
let deref_tsets tsets = Cil.mkTermMem ~addr:tsets ~off:TNoOffset

(* returns true iff the logic variable is defined by the
   Frama-C standard library *)
let comes_from_fc_stdlib lvar =
  Cil.is_in_libc lvar.lv_attr ||
  match lvar.lv_origin with
  | None -> false
  | Some vi ->
    Cil.is_in_libc vi.vattr

let is_noop_cast ~src_typ ~dst_typ =
  let src_typ = Logic_const.plain_or_set
      (fun lt ->
         match Logic_utils.unroll_type lt with
         | Ctype typ -> Eval_typ.classify_as_scalar typ
         | _ -> None
      ) (Logic_utils.unroll_type src_typ)
  in
  let open Eval_typ in
  match src_typ, Eval_typ.classify_as_scalar dst_typ with
  | Some (TSInt rsrc), Some (TSInt rdst) ->
    Eval_typ.range_inclusion rsrc rdst
  | Some (TSFloat srckind), Some (TSFloat destkind) ->
    Cil.frank srckind <= Cil.frank destkind
  | Some (TSPtr _), Some (TSPtr _) -> true
  | _ -> false

(* If casting [trm] to [typ] has no effect in terms of the values contained
   in [trm], do nothing. Otherwise, raise [exn]. Adapted from [pass_cast] *)
let pass_logic_cast exn typ trm =
  match Logic_utils.unroll_type typ, Logic_utils.unroll_type trm.term_type with
  | Linteger, Ctype { tnode = (TInt _ | TEnum _) } -> () (* Always inclusion *)
  | Ctype ({ tnode = (TInt _ | TEnum _) } as typ),
    Ctype ({ tnode = (TInt _ | TEnum _) } as typeoftrm) ->
    let sztyp = Bit_utils.sizeof typ in
    let szexpr = Bit_utils.sizeof typeoftrm in
    let styp, sexpr =
      match sztyp, szexpr with
      | Int_Base.Value styp, Int_Base.Value sexpr -> styp, sexpr
      | _ -> raise exn
    in
    let sityp = Bit_utils.is_signed_int_enum_pointer typ in
    let sisexpr = Bit_utils.is_signed_int_enum_pointer typeoftrm in
    if (Integer.ge styp sexpr && sityp = sisexpr) (* larger, same signedness *)
    || (Integer.gt styp sexpr && sityp) (* strictly larger and signed *)
    then ()
    else raise exn

  | Lreal,  Ctype { tnode = (TFloat _) } -> () (* Always inclusion *)
  | Ctype { tnode = (TFloat f1) }, Ctype { tnode = (TFloat f2) } ->
    if Cil.frank f1 < Cil.frank f2
    then raise exn

  | _ -> raise exn (* Not a scalar type *)

let is_same_term_coerce t1 t2 =
  match t1.term_node, t2.term_node with
  | TCast (true,_,_), TCast (true,_,_) -> Logic_utils.is_same_term t1 t2
  | TCast (true, _,t1), _ -> Logic_utils.is_same_term t1 t2
  | _, TCast (true, _,t2) -> Logic_utils.is_same_term t1 t2
  | _ -> Logic_utils.is_same_term t1 t2

(* Constrain the ACSL range [idx] when it is used to access an array of
   [size_arr] cells, and it is a Trange in which one size is not
   specified. (E.g. t[1..] is transformed into t[1..9] when t has size 10). *)
let constraint_trange idx size_arr =
  if Kernel.SafeArrays.get () then
    match idx.term_node with
    | Trange ((None as low), up) | Trange (low, (None as up)) -> begin
        let loc = idx.term_loc in
        match Option.bind size_arr Cil.constFoldToInt with
        | None -> idx
        | Some size ->
          let low = match low with (* constrained l.h.s *)
            | Some _ -> low
            | None -> Some (Logic_const.tint ~loc Integer.zero)
          in
          let up = match up with (* constrained r.h.s *)
            | Some _ -> up
            | None -> Some (Logic_const.tint ~loc (Integer.pred size))
          in
          Logic_const.trange ~loc (low, up)
      end
    | _ -> idx
  else idx

(* -------------------------------------------------------------------------- *)
(* --- Inlining of defined logic functions and predicates                 --- *)
(* -------------------------------------------------------------------------- *)

type pred_fun_origin = ACSL | Libc

let known_logic_funs = [
  "strlen", Libc;
  "wcslen", Libc;
  "strchr", Libc;
  "wcschr", Libc;
  "memchr_off", Libc;
  "wmemchr_off", Libc;
  "atan2", ACSL;
  "atan2f", ACSL;
  "pow", ACSL;
  "powf", ACSL;
  "fmod", ACSL;
  "fmodf", ACSL;
  "sqrt", ACSL;
  "sqrtf", ACSL;
  "\\sign", ACSL;
  "\\min", ACSL;
  "\\max", ACSL;
  "\\abs", ACSL;
  "\\neg_float",ACSL;
  "\\add_float",ACSL;
  "\\sub_float",ACSL;
  "\\mul_float",ACSL;
  "\\div_float",ACSL;
  "\\neg_double",ACSL;
  "\\add_double",ACSL;
  "\\sub_double",ACSL;
  "\\mul_double",ACSL;
  "\\div_double",ACSL;
]
let known_predicates = [
  "\\warning", ACSL;
  "\\is_finite", ACSL;
  "\\is_infinite", ACSL;
  "\\is_plus_infinity", ACSL;
  "\\is_minus_infinity", ACSL;
  "\\is_NaN", ACSL;
  "\\eq_float", ACSL;
  "\\ne_float", ACSL;
  "\\lt_float", ACSL;
  "\\le_float", ACSL;
  "\\gt_float", ACSL;
  "\\ge_float", ACSL;
  "\\eq_double", ACSL;
  "\\ne_double", ACSL;
  "\\lt_double", ACSL;
  "\\le_double", ACSL;
  "\\gt_double", ACSL;
  "\\ge_double", ACSL;
  "\\subset", ACSL;
  "\\tainted", ACSL;
  "valid_read_string", Libc;
  "valid_string", Libc;
  "valid_read_wstring", Libc;
  "valid_wstring", Libc;
  "is_allocable", Libc;
]

let is_known_logic_fun_pred known lvi =
  try
    let origin = List.assoc lvi.lv_name known in
    match origin with
    | ACSL -> true
    | Libc -> comes_from_fc_stdlib lvi
  with Not_found -> false

let is_known_logic_fun = is_known_logic_fun_pred known_logic_funs
let is_known_predicate = is_known_logic_fun_pred known_predicates

let inline logic_info =
  let logic_var = logic_info.l_var_info in
  not (is_known_logic_fun logic_var || is_known_predicate logic_var)

(* -------------------------------------------------------------------------- *)
(* --- Auxiliary evaluation functions                                     --- *)
(* -------------------------------------------------------------------------- *)

(* We evaluate the ACSL sign type as integers 1 or -1. Sign values can only be
   constructed through the \sign function (handled in eval_known_logic_function)
   and the \Positive and \Negative constructors (handled in eval_term). They can
   only be compared through equality and disequality; no other operation exists
   on this type, so our interpretation remains correct. *)
let positive_cvalue = Cvalue.V.inject_int Integer.one
let negative_cvalue = Cvalue.V.inject_int Integer.minus_one

let is_true = function
  | `True | `TrueReduced _ -> true
  | `Unknown _ | `False | `Unreachable -> false

(* Check "logic alarms" when evaluating [v1 op v2]. All operators shifts are
   defined unambiguously in ACSL. *)
let check_logic_alarms ~alarm_mode typ (_v1: V.t eval_result) op v2 =
  match op with
  | Div | Mod when Cil.isIntegralOrPointerType typ ->
    let truth = Cvalue_forward.assume_non_zero v2.eover in
    let division_by_zero = not (is_true truth) in
    track_alarms division_by_zero alarm_mode
  | Shiftlt | Shiftrt -> begin
      (* Check that [e2] is positive. [e1] can be arbitrary, we use
         the arithmetic vision of shifts *)
      try
        let i2 = Cvalue.V.project_ival_bottom v2.eover in
        let valid = Ival.is_included i2 Ival.positive_integers in
        track_alarms (not valid) alarm_mode
      with Cvalue.V.Not_based_on_null -> track_alarms true alarm_mode
    end
  | _ -> ()

(* As usual in this file, [dst_typ] may be misleading: the 'size' is
   meaningless, because [src_typ] may actually be a logic type. Thus,
   this size must not be used below. *)
let cast ~src_typ ~dst_typ v =
  let open Eval_typ in
  match classify_as_scalar dst_typ, classify_as_scalar src_typ with
  | None, _ | _, None  -> v (* unclear whether this happens. *)
  | Some dst, Some src ->
    match dst, src with
    | TSFloat fkind, (TSInt _ | TSPtr _) ->
      Cvalue.V.cast_int_to_float (Fval.kind fkind) v

    | (TSInt dst | TSPtr dst), TSFloat fkind ->
      (* This operation is not fully defined in ACSL. We raise an alarm
         in case of overflow. *)
      if is_true (Cvalue_forward.assume_not_nan ~assume_finite:true fkind v)
      then Cvalue_forward.cast_float_to_int dst v
      else c_alarm ()

    | (TSInt dst | TSPtr dst), (TSInt _ | TSPtr _) ->
      let size = Integer.of_int dst.i_bits in
      let signed = dst.i_signed in
      V.cast_int_to_int ~signed ~size v

    | TSFloat fkind, TSFloat _ ->
      Cvalue.V.cast_float_to_float (Fval.kind fkind) v

(* V.cast_int_to_int is unsound when the destination type is _Bool.
   Use this function instead. *)
let cast_to_bool r =
  let contains_zero = V.contains_zero r.eover
  and contains_non_zero = V.contains_non_zero r.eover in
  let eover = V.interp_boolean ~contains_zero ~contains_non_zero in
  { eover; eunder = under_from_over eover; empty = r.empty;
    ldeps = r.ldeps; etype = Cil_const.boolType }

(* Note: "charlen" stands for either strlen or wcslen *)

(* Applies a cvalue [builtin] to the list of arguments [args_list] in the
   current state of [env]. Returns [v, alarms], where [v] is the resulting
   cvalue, which can be bottom. *)
let apply_logic_builtin builtin env args_list =
  (* the call below could in theory return Builtins.Invalid_nb_of_args,
     but logic typing constraints prevent that. *)
  builtin (env_current_state env) args_list

(* Never raises exceptions; instead, returns [-1,+oo] in case of alarms
   (most imprecise result possible for the logic strlen/wcslen predicates). *)
let eval_logic_charlen wrapper env v ldeps =
  let eover =
    let v, alarms = apply_logic_builtin wrapper env [v] in
    if alarms && not (Cvalue.V.is_bottom v)
    then Cvalue.V.inject_ival (Ival.inject_range (Some Integer.minus_one) None)
    else v
  in
  let eunder = under_from_over eover in
  (* the C strlen function has type size_t, but the logic strlen function has
     type ℤ (signed) *)
  let etype = Cil_const.intType in
  { etype; ldeps; eover; empty = false; eunder }

(* Evaluates the logical predicates strchr/wcschr. *)
let eval_logic_charchr builtin env s c ldeps_s ldeps_c =
  let eover =
    let v, alarms = apply_logic_builtin builtin env [s; c] in
    if Cvalue.V.is_bottom v then v else
    if alarms then Cvalue.V.zero_or_one else
      let ctrue = Cvalue.V.contains_non_zero v
      and cfalse = Cvalue.V.contains_zero v in
      match ctrue, cfalse with
      | true, true -> Cvalue.V.zero_or_one
      | true, false -> Cvalue.V.singleton_one
      | false, true -> Cvalue.V.singleton_zero
      | false, false -> assert false (* a logic alarm would have been raised*)
  in
  let eunder = under_from_over eover in
  (* the C strchr function has type char*, but the logic strchr predicate has
     type 𝔹 *)
  let etype = Cil_const.boolType in
  let ldeps = join_logic_deps ldeps_s ldeps_c in
  { etype; ldeps; eover; empty = false; eunder }

(* Evaluates the logical functions memchr_off/wmemchr_off. *)
let eval_logic_memchr_off builtin env s c n =
  let minus_one = Cvalue.V.inject_int Integer.minus_one in
  let positive = Cvalue.V.inject_ival Ival.positive_integers in
  let pred_n = Cvalue.V.add_untyped ~factor:Int_Base.one n.eover minus_one in
  let n_pos = Cvalue.V.narrow positive pred_n in
  let eover =
    if Cvalue.V.is_bottom n_pos then minus_one else
      let args = [s.eover; c.eover; n_pos] in
      let v, alarms = apply_logic_builtin builtin env args in
      if Cvalue.V.is_bottom v then pred_n else
      if alarms then Cvalue.V.join pred_n v else
      if Cvalue.V.equal n_pos pred_n then v else
        Cvalue.V.join minus_one v
  in
  let ldeps = join_logic_deps s.ldeps (join_logic_deps c.ldeps n.ldeps) in
  { (einteger eover) with ldeps }

(* Evaluates the logical predicate is_allocable, according to the following
   logic:
   - if the size to allocate is always too large (> SIZE_MAX), allocation fails;
   - otherwise, if AllocReturnsNull is true or if the size may exceed SIZE_MAX,
     returns Unknown (to simulate non-determinism);
   - otherwise, allocation always succeeds. *)
let eval_is_allocable size =
  let size_ok = Builtins_malloc.alloc_size_ok size in
  match size_ok, Parameters.AllocReturnsNull.get () with
  | Alarmset.False, _ -> False
  | Alarmset.Unknown, _ | _, true -> Unknown
  | Alarmset.True, false -> True

(* Evaluates a [valid_read_string] or [valid_read_wstring] predicate
   using str* builtins.
   - if [bottom] is obtained, return False;
   - otherwise, if no alarms are emitted, return True;
   - otherwise, return [Unknown]. *)
let eval_valid_read_str ~wide env v =
  let wrapper =
    if wide then Builtins_string.frama_c_wcslen_wrapper
    else Builtins_string.frama_c_strlen_wrapper
  in
  let v, alarms = apply_logic_builtin wrapper env [v] in
  if Cvalue.V.is_bottom v
  then False (* bottom state => string always invalid *)
  else if alarms
  then Unknown (* alarm => string possibly invalid *)
  else True (* no alarm => string always valid for reading *)

(* Evaluates a [valid_string] or [valid_wstring] predicate.
   First, we check the constness of the arguments.
   Then, we evaluate [valid_read_string/valid_read_wstring] on non-const ones. *)
let eval_valid_str ~wide env v =
  assert (not (Cvalue.V.is_bottom v));
  (* filter const bases *)
  let v' = Cvalue.V.filter_base (fun b -> not (Base.is_read_only b)) v in
  if Cvalue.V.is_bottom v' then False (* all bases were const *)
  else
  if Cvalue.V.equal v v' then
    eval_valid_read_str ~wide env v (* all bases non-const *)
  else (* at least one base was const *)
    match eval_valid_read_str ~wide env v with
    | True -> Unknown (* weaken result *)
    | False | Unknown as r -> r

(* Do all the possible values of a location in [state] satisfy [test]?  [loc] is
   an over-approximation of the location, so the answer cannot be [False] even
   if some parts of [loc] do not satisfy [test]. Thus, this function does not
   fold the location, but instead applies [test] to the join of all values
   stored in [loc] in [state].  *)
let forall_in_over_location state loc test =
  let v = Model.find_indeterminate state loc in
  test v

(* Do all the possible values of a location in [state] satisfy [test]?  [loc] is
   an under-approximation of the location, so the answer cannot be [True], as
   the values of some other parts of the location may not satisfy [test].
   However, it is [False] as soon as some part of [loc] contradicts [test]. *)
let forall_in_under_location state loc test =
  let exception EFalse in
  let inspect_value (_, _) (value, _, _) acc =
    match test value with
    | True | Unknown -> acc
    | False -> raise EFalse
  in
  let inspect_itv base itv acc =
    match Cvalue.Model.find_base_or_default base state with
    | `Top | `Bottom -> Unknown
    | `Value offsm ->
      Cvalue.V_Offsetmap.fold_between ~entire:true itv inspect_value offsm acc
  in
  let inspect_base base intervals acc =
    Int_Intervals.fold (inspect_itv base) intervals acc
  in
  let zone = Locations.enumerate_bits loc in
  try Zone.fold_i inspect_base zone Unknown
  with EFalse -> False
     | Abstract_interp.Error_Top -> Unknown

(* Evaluates an universal predicate about the values of a location evaluated to
   [r] in [state]. The predicates holds whenever all the possible values at the
   location satisfy [test]. *)
let eval_forall_predicate state r test =
  let size_bits = Eval_typ.sizeof_lval_typ r.etype in
  let make_loc loc = make_loc loc size_bits in
  let over_loc = make_loc r.eover in
  if not Locations.(is_valid Read over_loc) then c_alarm ();
  match forall_in_over_location state over_loc test with
  | Unknown ->
    let under_loc = make_loc r.eunder in
    forall_in_under_location state under_loc test
  | True -> True
  | False -> if r.empty then Unknown else False

(* Evaluation of an \initialized predicate on a location evaluated to [r]
   in the state [state]. *)
let eval_initialized state r =
  let test = function
    | V_Or_Uninitialized.C_init_esc _
    | V_Or_Uninitialized.C_init_noesc _ -> True
    | V_Or_Uninitialized.C_uninit_esc _ -> Unknown
    | V_Or_Uninitialized.C_uninit_noesc v ->
      if Location_Bytes.is_bottom v then False else Unknown
  in
  eval_forall_predicate state r test

(* Evaluation of a \dangling predicate on a location evaluated to [r]
   in the state [state]. *)
let eval_dangling state r =
  let test = function
    | V_Or_Uninitialized.C_init_esc v ->
      if Location_Bytes.is_bottom v then True else Unknown
    | V_Or_Uninitialized.C_uninit_esc _ -> Unknown
    | V_Or_Uninitialized.C_init_noesc _
    | V_Or_Uninitialized.C_uninit_noesc _ -> False
  in
  eval_forall_predicate state r test

(* -------------------------------------------------------------------------- *)
(* --- Evaluation of terms                                                --- *)
(* -------------------------------------------------------------------------- *)

exception Reduce_to_bottom

let int_or_float_op typ int_op float_op =
  match typ.tnode with
  | TInt _ | TPtr _ | TEnum _ -> int_op
  | TFloat _fk -> float_op
  | _ -> ast_error (Format.asprintf
                      "binop on incorrect type %a" Printer.pp_typ typ)

let forward_binop_by_type typ =
  let forward_int = Cvalue_forward.forward_binop_int ~typ
  and forward_float = Cvalue_forward.forward_binop_float Fval.Real in
  int_or_float_op typ forward_int forward_float

let forward_binop typ v1 op v2 =
  match op with
  | Eva_ast.Eq | Ne | Le | Lt | Ge | Gt ->
    let comp = Eva_ast.conv_relation op in
    if Cil.isPointerType typ || Cvalue_forward.are_comparable comp v1 v2
    then forward_binop_by_type typ v1 op v2
    else Cvalue.V.zero_or_one
  | _ -> forward_binop_by_type typ v1 op v2

let rec eval_term ~alarm_mode env t =
  match t.term_node with
  | Tat (t, lab) ->
    ignore (env_state env lab);
    eval_term ~alarm_mode { env with e_cur = lab } t

  | TConst (Boolean true) -> einteger Cvalue.V.singleton_one
  | TConst (Boolean false) -> einteger Cvalue.V.singleton_zero
  | TConst (Integer (v, _)) -> einteger (Cvalue.V.inject_int v)
  | TConst (LEnum e) ->
    (match Cil.constFoldToInt e.eival with
     | Some v -> einteger (Cvalue.V.inject_int v)
     | _ -> ast_error "non-evaluable constant")
  | TConst (LChr c) ->
    einteger (Cvalue.V.inject_int (Cil.charConstToInt c))
  | TConst (LReal { r_nearest; r_lower ; r_upper }) -> begin
      if Fc_float.is_nan r_nearest
      then ereal Fval.nan
      else
        let r_lower = Fval.F.of_float r_lower in
        let r_upper = Fval.F.of_float r_upper in
        let f = Fval.inject Fval.Real r_lower r_upper in
        ereal f
    end

  (*  | TConst ((CStr | CWstr) Missing cases *)

  | TAddrOf tlval ->
    let r = eval_tlval ~alarm_mode env tlval in
    { etype = Cil_const.mk_tptr r.etype;
      ldeps = r.ldeps;
      eunder = loc_bits_to_loc_bytes_under r.eunder;
      eover = loc_bits_to_loc_bytes r.eover;
      empty = r.empty; }

  | TStartOf tlval ->
    let r = eval_tlval ~alarm_mode env tlval in
    { etype = Cil_const.mk_tptr (Cil.typeOf_array_elem r.etype);
      ldeps = r.ldeps;
      eunder = loc_bits_to_loc_bytes_under r.eunder;
      eover = loc_bits_to_loc_bytes r.eover;
      empty = r.empty; }

  (* Special case for the constants \pi, \e, \infinity and \NaN. *)
  | TLval (TVar {lv_name = "\\pi"}, _) -> ereal Fval.pi
  | TLval (TVar {lv_name = "\\e"}, _)  -> ereal Fval.e
  | TLval (TVar {lv_name = "\\plus_infinity"}, _) ->
    efloat Fval.(pos_infinity Single)
  | TLval (TVar {lv_name = "\\minus_infinity"}, _) ->
    efloat Fval.(neg_infinity Single)
  | TLval (TVar {lv_name = "\\NaN"}, _) -> efloat Fval.nan

  (* Mathematical logic variable: uses the [logic_vars] environment. *)
  | TLval (TVar ({ lv_type = Linteger | Lreal } as logic_var), TNoOffset) ->
    let cvalue = LogicVarEnv.find logic_var env.logic_vars in
    if logic_var.lv_type = Linteger
    then einteger cvalue
    else inject_no_deps Cil_const.doubleType cvalue

  | TLval tlval ->
    let lval = eval_tlval ~alarm_mode env tlval in
    let typ = lval.etype in
    let size = Eval_typ.sizeof_lval_typ typ in
    let state = env_current_state env in
    let eover_loc = make_loc (lval.eover) size in
    let eover = find_or_alarm ~alarm_mode state eover_loc in
    let eover = Cvalue_forward.make_volatile ~typ eover in
    let eover = Cvalue_forward.reinterpret typ eover in
    (* Skip dependencies if state is dead *)
    let deps =
      if Cvalue.Model.is_reachable state then
        add_deps env.e_cur empty_logic_deps
          (enumerate_valid_bits Locations.Read eover_loc)
      else empty_logic_deps
    in
    let eunder_loc = make_loc (lval.eunder) size in
    let eunder =
      match Eval_op.find_under_approximation state eunder_loc with
      | Some eunder -> V_Or_Uninitialized.get_v eunder
      | None -> under_from_over eover
    in
    { etype = typ;
      ldeps = join_logic_deps deps (lval.ldeps);
      eunder; eover; empty = lval.empty; }

  (* TBinOp ((LOr | LAnd), _t1, _t2) -> TODO: a special case would be useful.
     But this requires reducing the state after having evaluated t1 by
     a term that is in fact a predicate *)
  | TBinOp (op,t1,t2) -> eval_binop ~alarm_mode env op t1 t2

  | TUnOp (op, t) ->
    let r = eval_term ~alarm_mode env t in
    let typ' = match op with
      | Neg -> r.etype
      | BNot -> r.etype (* can only be used on an integer type *)
      | LNot -> Cil_const.intType
    in
    let op = Eva_ast.translate_unop op in
    let v = Cvalue_forward.forward_unop r.etype op r.eover in
    let eover = v in
    { etype = typ';
      ldeps = r.ldeps;
      eover; eunder = under_from_over eover; empty = r.empty; }

  | Trange (opt_low, opt_high) ->
    (* The overapproximation is the range [min(low.eover)..max(high.eover)].
       The underapproximation is the range [max(low.eover)..min(high.eover)].
       Perhaps surprisingly, we do not use the under-approximations of
       otlow and othigh to compute the underapproximation. We could
       potentially compute [min(max(low.over),  min(low.under) ..
                            max(min(high.over), max(high.under)]
       However, tsets cannot be used as bounds of ranges. By invariant (2),
       eunder is either Bottom, or equal to eover, both being of cardinal
       one. In both cases, using eover is more precise. *)
    let deps = ref empty_logic_deps in
    (* Evaluates the minimum and maximum integer value of an optional term.
       According to the Ival convention, None stands for -∞ and ∞ for the
       minimum and maximum respectively. *)
    let min_max = function
      | None -> None, None
      | Some term ->
        try
          let result = eval_term ~alarm_mode env term in
          deps := join_logic_deps !deps result.ldeps;
          let ival = Cvalue.V.project_ival result.eover in
          Ival.min_int ival, Ival.max_int ival
        with
        | Cvalue.V.Not_based_on_null -> None, None
        | LogicEvalError e ->
          if e <> CAlarm then
            Self.result ~source:(fst t.term_loc) ~once:true
              "@[<hov 0>Cannot evaluate@ range bound %a@ (%a). Approximating@]"
              Printer.pp_term term pretty_logic_evaluation_error e;
          None, None
    in
    let min_over, min_under = min_max opt_low
    and max_under, max_over = min_max opt_high in
    let eover = Cvalue.V.inject_ival (Ival.inject_range min_over max_over) in
    (* Beware: [None] stands for positive infinity for [min_under] and for
       negative infiny for [max_under] (in which case eunder must be bottom),
       except when the bound was itself [None]. *)
    let eunder =
      if (opt_low <> None && min_under = None)
      || (opt_high <> None && max_under = None)
      then Cvalue.V.bottom
      else Cvalue.V.inject_ival (Ival.inject_range min_under max_under)
    in
    let empty = Cvalue.V.is_bottom eunder in
    { ldeps = !deps;
      etype = Cil_const.intType;
      eunder; eover; empty; }

  | TCast (false, Ctype typ, t) ->
    let r = eval_term ~alarm_mode env t in
    (* See if the cast does something. If not, we can keep eunder as is.*)
    if is_noop_cast ~src_typ:t.term_type ~dst_typ:typ
    then { r with etype = typ }
    else if Cil.isBoolType typ
    then cast_to_bool r
    else
      let eover = cast ~src_typ:r.etype ~dst_typ:typ r.eover in
      { etype = typ; ldeps = r.ldeps; eunder = under_from_over eover; eover;
        empty = r.empty; }
  | TCast (false, _,_) -> assert false

  | TCast (true, ltyp, t) ->
    let r = eval_term ~alarm_mode env t in
    (* we must handle coercion from singleton to set, for which there is
       nothing to do, AND coercion from an integer type to a floating-point
       type, that require a conversion. *)
    (match Logic_const.plain_or_set Fun.id ltyp with
     | Linteger when Logic_typing.is_integral_type t.term_type
                  || Logic_const.is_boolean_type t.term_type -> r
     | Ctype typ when Cil.isIntegralOrPointerType typ -> r
     | Lreal ->
       let eover =
         if Logic_typing.is_integral_type t.term_type
         then V.cast_int_to_float Fval.Real r.eover
         else V.cast_float_to_float Fval.Real r.eover
       in
       { etype = Cil_const.longDoubleType; (* hack until logic type *)
         ldeps = r.ldeps;
         eover; eunder = under_from_over eover;
         empty = r.empty }
     | ltyp ->
       if Logic_const.is_boolean_type ltyp
       && Logic_typing.is_integral_type t.term_type
       then cast_to_bool r
       else if Logic_utils.is_same_type ltyp t.term_type then
         (* coercion from singleton to set *)
         r
       else
         unsupported
           (Format.asprintf "logic coercion %a -> %a@."
              Printer.pp_logic_type t.term_type Printer.pp_logic_type ltyp)
    )

  | Tif (tcond, ttrue, tfalse) ->
    eval_tif eval_term Cvalue.V.join Cvalue.V.meet ~alarm_mode env
      tcond ttrue tfalse

  | TSizeOf _ | TSizeOfE _ | TSizeOfStr _ | TAlignOf _ | TAlignOfE _ ->
    let e = Cil.constFoldTerm t in
    let v = match e.term_node with
      | TConst (Integer (v, _)) -> Cvalue.V.inject_int v
      | _ -> V.top_int
    in
    einteger v

  | Tunion l ->
    let eunder, eover, deps, empty = List.fold_left
        (fun (accunder, accover, accdeps, accempty) t ->
           let r = eval_term ~alarm_mode env t in
           (Cvalue.V.link accunder r.eunder,
            Cvalue.V.join accover r.eover,
            join_logic_deps accdeps r.ldeps,
            accempty || r.empty))
        (Cvalue.V.bottom, Cvalue.V.bottom, empty_logic_deps, false) l
    in
    { etype = infer_type t.term_type;
      ldeps = deps; eunder; eover; empty; }

  | Tempty_set ->
    { etype = infer_type t.term_type;
      ldeps = empty_logic_deps;
      eunder = Cvalue.V.bottom;
      eover = Cvalue.V.bottom;
      empty = true; }

  | Tnull ->
    { etype = Cil_const.voidPtrType;
      ldeps = empty_logic_deps;
      eunder = Cvalue.V.singleton_zero;
      eover = Cvalue.V.singleton_zero;
      empty = false; }

  (* TODO: the meaning of the label in \offset and \base_addr is not obvious
     at all *)
  | Toffset (_lbl, t) ->
    let r = eval_term ~alarm_mode env t in
    let add_offset _ offs acc = Ival.join offs acc in
    let offs =
      try Location_Bytes.fold_topset_ok add_offset r.eover Ival.bottom
      with Abstract_interp.Error_Top -> Ival.top
    in
    let eover = Cvalue.V.inject_ival offs in
    { etype = Cil_const.intType;
      ldeps = r.ldeps;
      eover;
      eunder = under_from_over eover;
      empty = r.empty; }

  | Tbase_addr (_lbl, t) ->
    let r = eval_term ~alarm_mode env t in
    let add_base b acc = V.join acc (V.inject b Ival.zero) in
    let eover =
      try Location_Bytes.fold_bases add_base r.eover V.bottom
      with Abstract_interp.Error_Top -> r.eover
    in
    { etype = Cil_const.charPtrType;
      ldeps = r.ldeps;
      eover;
      eunder = under_from_over eover;
      empty = r.empty; }

  | Tblock_length (_lbl, t) -> (* TODO: take label into account for locals *)
    let r = eval_term ~alarm_mode env t in
    let add_block_length b acc =
      let bl =
        (* Convert the validity frontiers into a range of bytes. The
           frontiers are always 0 or 8*k-1 (because validity is in bits and
           starts on zero), so we add 1 everywhere, then divide by eight. *)
        let convert start_bits end_bits =
          let congr_succ i = Integer.(equal zero (e_rem (succ i) eight)) in
          let congr_or_zero i = Integer.(equal zero i || congr_succ i) in
          assert (congr_or_zero start_bits || congr_or_zero end_bits);
          let start_bytes = Integer.(e_div (Integer.succ start_bits) eight) in
          let end_bytes =   Integer.(e_div (Integer.succ end_bits)   eight) in
          Ival.inject_range (Some start_bytes) (Some end_bytes)
        in
        match Base.validity b with
        | Base.Empty -> Ival.zero
        | Base.Invalid -> Ival.top (* we may also emit an alarm *)
        | Base.Known (_, ma) -> convert ma ma
        | Base.Unknown (mi, None, ma) -> convert mi ma
        | Base.Unknown (_, Some mi, ma) -> convert mi ma
        | Base.Variable weak_v ->
          convert weak_v.Base.min_alloc weak_v.Base.max_alloc
      in
      Ival.join acc bl
    in
    let bl =
      try Location_Bytes.fold_bases add_block_length r.eover Ival.bottom
      with Abstract_interp.Error_Top -> Ival.top
    in
    let eover = V.inject_ival bl in
    { etype = Cil_const.charPtrType;
      ldeps = r.ldeps;
      eover;
      eunder = under_from_over eover;
      empty = r.empty; }

  | Tapp (li, labels, args) -> begin
      if is_known_logic_fun li.l_var_info then
        eval_known_logic_function ~alarm_mode env li labels args
      else
        match Inline.inline_term ~inline ~current:env.e_cur t with
        | Some t' -> eval_term ~alarm_mode env t'
        | None ->
          let s =
            Format.asprintf "logic function %a"
              Printer.pp_logic_var li.l_var_info
          in
          unsupported s
    end

  | TDataCons (ctor_info, _) ->
    begin
      match ctor_info.ctor_name with
      | "\\Positive" -> einteger positive_cvalue
      | "\\Negative" -> einteger negative_cvalue
      | _ -> unsupported "logic inductive types"
    end

  | Tcomprehension (term, quantifiers, predicate) ->
    let env = bind_comprehension_quantifiers env quantifiers predicate in
    let r = eval_term ~alarm_mode env term in
    let pred_deps = Option.bind predicate (predicate_deps env) in
    let ldeps =
      Option.fold ~none:r.ldeps ~some:(join_logic_deps r.ldeps) pred_deps
    in
    { r with empty = true; ldeps }

  | Tlet (li, t') ->
    let env = eval_let_binding ~alarm_mode env li in
    eval_term ~alarm_mode env t'

  | Tlambda _ -> unsupported "logic functions or predicates"
  | TUpdate _ -> unsupported "functional updates"
  | Ttype _ -> unsupported "\\type operator"
  | Ttypeof _ -> unsupported "\\typeof operator"
  | Tinter _ -> unsupported "set intersection"
  | TConst (LStr _) -> unsupported "constant strings"
  | TConst (LWStr _) -> unsupported "wide constant strings"

and eval_binop ~alarm_mode env op t1 t2 =
  if not (isLogicNonCompositeType t1.term_type) then
    if Self.debug_atleast 1 then
      unsupported (Format.asprintf
                     "operation (%a) %a (%a) on non-supported type %a"
                     Printer.pp_term t1
                     Printer.pp_binop op
                     Printer.pp_term t2
                     Printer.pp_logic_type t1.term_type)
    else
      unsupported (Format.asprintf
                     "%a operation on non-supported type %a"
                     Printer.pp_binop op
                     Printer.pp_logic_type t1.term_type)
  else
    let r1 = eval_term ~alarm_mode env t1 in
    let r2 = eval_term ~alarm_mode env t2 in
    let te1 = Cil.unrollType r1.etype in
    check_logic_alarms ~alarm_mode te1 r1 op r2;
    let typ_res = infer_binop_res_type op te1 in
    let op = Eva_ast.translate_binop op in
    let eover = forward_binop te1 r1.eover op r2.eover in
    let default _r1 _r2 = under_from_over eover in
    let add_untyped_op factor =
      int_or_float_op te1 (V.add_untyped_under ~factor) default
    in
    let eunder_op = match op with
      | PlusPI -> begin
          match Bit_utils.osizeof_pointed te1 with
          | Int_Base.Top -> fun _ _ -> V.bottom
          | Int_Base.Value _ as size -> add_untyped_op size
        end
      | PlusA -> add_untyped_op (Int_Base.one)
      | MinusA -> add_untyped_op (Int_Base.minus_one)
      | _ -> fun _ _ -> under_from_over eover
    in
    let eunder = eunder_op r1.eunder r2.eunder in
    { etype = typ_res;
      ldeps = join_logic_deps r1.ldeps r2.ldeps;
      eunder; eover; empty = r1.empty || r2.empty; }

and eval_tif : 'a. (alarm_mode:_ -> _ -> _ -> 'a eval_result) -> ('a -> 'a -> 'a) -> ('a -> 'a -> 'a) -> alarm_mode:_ -> _ -> _ -> _ -> _ -> 'a eval_result =
  fun eval join meet ~alarm_mode env tcond ttrue tfalse ->
  let r = eval_term ~alarm_mode env tcond in
  let ctrue =  Cvalue.V.contains_non_zero r.eover
  and cfalse =  Cvalue.V.contains_zero r.eover in
  match ctrue, cfalse with
  | true, true ->
    let vtrue = eval ~alarm_mode env ttrue in
    let vfalse = eval ~alarm_mode env tfalse in
    if not (same_etype vtrue.etype vfalse.etype) then
      Self.failure ~current:true
        "Incoherent types in conditional: %a vs. %a. \
         Please report"
        Printer.pp_typ vtrue.etype Printer.pp_typ vfalse.etype;
    let eover = join vtrue.eover vfalse.eover in
    let eunder = meet vtrue.eunder vfalse.eunder in
    { etype = vtrue.etype;
      ldeps = join_logic_deps vtrue.ldeps vfalse.ldeps;
      eunder; eover; empty = vtrue.empty || vfalse.empty; }
  | true, false  -> eval ~alarm_mode env ttrue
  | false, true  -> eval ~alarm_mode env tfalse
  | false, false -> assert false (* a logic alarm would have been raised*)

(* if you add something here, update known_logic_funs above also *)
and eval_known_logic_function ~alarm_mode env li labels args =
  let lvi = li.l_var_info in
  match lvi.lv_name, li.l_type, labels, args with
  | ("strlen" | "wcslen") as b,  _, [lbl], [arg] ->
    begin
      match arg.term_node with
      | TConst (LStr str) ->
        (* Look for the first occurrence of the '\0' character, otherwise
           return the string length. *)
        let length =
          try String.index str '\x00'
          with Not_found -> String.length str
        in
        einteger (Cvalue.V.inject_int (Integer.of_int length))
      | _ ->
        let r = eval_term ~alarm_mode env arg in
        let builtin =
          if b = "strlen" then Builtins_string.frama_c_strlen_wrapper
          else Builtins_string.frama_c_wcslen_wrapper
        in
        eval_logic_charlen builtin { env with e_cur = lbl } r.eover r.ldeps
    end
  | ("memchr_off" | "wmemchr_off") as b,  _, [lbl], [arg_s; arg_c; arg_n] ->
    let s = eval_term ~alarm_mode env arg_s in
    let c = eval_term ~alarm_mode env arg_c in
    let n = eval_term ~alarm_mode env arg_n in
    let builtin =
      if b = "memchr_off"
      then Builtins_string.frama_c_memchr_off_wrapper
      else Builtins_string.frama_c_wmemchr_off_wrapper
    in
    eval_logic_memchr_off builtin { env with e_cur = lbl } s c n

  | ("strchr" | "wcschr") as b,  _, [lbl], [arg_s; arg_c] ->
    let s = eval_term ~alarm_mode env arg_s in
    let c = eval_term ~alarm_mode env arg_c in
    let builtin =
      if b = "strchr" then Builtins_string.frama_c_strchr_wrapper
      else Builtins_string.frama_c_wcschr_wrapper
    in
    eval_logic_charchr builtin
      { env with e_cur = lbl } s.eover c.eover s.ldeps c.ldeps

  | ( "atan2" | "atan2f" | "fmod" | "fmodf" | "pow" | "powf"
    | "\\add_float" | "\\sub_float" | "\\mul_float" | "\\div_float"
    | "\\add_double" | "\\sub_double" | "\\mul_double" | "\\div_double" ),
    _, _, [arg1; arg2] ->
    eval_float_builtin_arity2 ~alarm_mode env lvi.lv_name arg1 arg2

  | ( "sqrt" | "sqrtf" | "\\neg_float" | "\\neg_double" ),_,_, [arg] ->
    eval_float_builtin_arity1 ~alarm_mode env lvi.lv_name arg

  | "\\sign", _, _, [arg] ->
    begin
      let r = eval_term ~alarm_mode env arg in
      try
        let fval = Cvalue.V.project_float r.eover in
        let sign = match Fval.is_negative fval with
          | True -> negative_cvalue
          | False -> positive_cvalue
          | Unknown -> Cvalue.V.join negative_cvalue positive_cvalue
        in
        { (einteger sign) with ldeps = r.ldeps }
      with Cvalue.V.Not_based_on_null -> c_alarm ()
    end

  | ("\\min" | "\\max"), Some typ, _, t1 :: t2 :: tail_args ->
    begin
      let min = lvi.lv_name = "\\min" in
      let comp = Abstract_interp.Comp.(if min then Le else Ge) in
      let backward =
        match typ with
        | Linteger -> Cvalue.V.backward_comp_int_left comp
        | Lreal -> Cvalue.V.backward_comp_float_left_true comp Fval.Real
        | _ -> assert false
      in
      let r1 = eval_term ~alarm_mode env t1
      and r2 = eval_term ~alarm_mode env t2 in
      (* If there are 2 arguments, it is the mathematical function \min or \max.
         If there are 3, it is the ACSL extended quantifiers \min or \max. *)
      match tail_args with
      | [] -> compute_extremum backward r1 r2
      | [ { term_node = Tlambda ([lv], term) } ] ->
        (* Evaluation of [term] when the logic variable [lv] is in [cvalue]. *)
        let eval_term cvalue =
          let env = add_logic_var env lv cvalue in
          eval_term ~alarm_mode env term
        in
        eval_quantifier_extremum backward ~min:r1 ~max:r2 eval_term
      | _ -> assert false
    end

  | "\\abs", Some typ, _, [t] ->
    begin
      let r = eval_term ~alarm_mode env t in
      try
        let ival = Cvalue.V.project_ival r.eover in
        let result =
          match typ with
          | Linteger -> einteger (Cvalue.V.inject_ival (Ival.abs_int ival))
          | Lreal -> ereal Fval.(abs Real (Ival.project_float ival))
          | _ -> assert false
        in
        { result with empty = r.empty; ldeps = r.ldeps; }
      with Cvalue.V.Not_based_on_null -> c_alarm ()
    end

  | _ -> assert false

and eval_float_builtin_arity2  ~alarm_mode env name arg1 arg2 =
  let fcaml = match name with
    | "atan2" ->  Fval.atan2 Fval.Double
    | "atan2f" -> Fval.atan2 Fval.Single
    | "fmod" ->   Fval.fmod  Fval.Double
    | "fmodf" ->  Fval.fmod  Fval.Single
    | "pow" ->    Fval.pow   Fval.Double
    | "powf" ->   Fval.pow   Fval.Single
    | "\\add_float" -> Fval.add Fval.Single
    | "\\sub_float" -> Fval.sub Fval.Single
    | "\\mul_float" -> Fval.mul Fval.Single
    | "\\div_float" -> Fval.div Fval.Single
    | "\\add_double" -> Fval.add Fval.Double
    | "\\sub_double" -> Fval.sub Fval.Double
    | "\\mul_double" -> Fval.mul Fval.Double
    | "\\div_double" -> Fval.div Fval.Double
    | _ -> assert false
  in
  let r1 = eval_term ~alarm_mode env arg1 in
  let r2 = eval_term ~alarm_mode env arg2 in
  let v =
    try
      let i1 = Cvalue.V.project_ival r1.eover in
      let f1 = Ival.project_float i1 in
      let i2 = Cvalue.V.project_ival r2.eover in
      let f2 = Ival.project_float i2 in
      Cvalue.V.inject_float (fcaml f1 f2)
    with Cvalue.V.Not_based_on_null ->
      Cvalue.V.topify Origin.Arith (V.join r1.eover r2.eover)
  in
  let eunder = under_from_over v in
  let ldeps = join_logic_deps r1.ldeps r2.ldeps in
  { etype = r1.etype; eunder; eover = v; ldeps; empty = r1.empty || r2.empty; }

and eval_float_builtin_arity1  ~alarm_mode env name arg =
  let fcaml = match name with
    | "sqrt" ->   Fval.sqrt  Fval.Double
    | "sqrtf" ->  Fval.sqrt  Fval.Single
    | "\\neg_float" | "\\neg_double" ->  Fval.neg
    | _ -> assert false
  in
  let r = eval_term ~alarm_mode env arg in
  let v =
    try
      let i = Cvalue.V.project_ival r.eover in
      let f = Ival.project_float i in
      Cvalue.V.inject_float (fcaml f)
    with Cvalue.V.Not_based_on_null ->
      Cvalue.V.topify Origin.Arith r.eover
  in
  let eunder = under_from_over v in
  { etype = r.etype; eunder; eover = v; ldeps=r.ldeps; empty = r.empty; }

(* Computes the max (resp. the min) between the evaluation results [r1] and [r2]
   according to [backward_left v1 v2] that reduces [v1] by assuming it is
   greater than (resp. lower than) [v2]. *)
and compute_extremum backward_left r1 r2 =
  let r1 = { r1 with eover = backward_left r1.eover r2.eover }
  and r2 = { r2 with eover = backward_left r2.eover r1.eover } in
  join_eval_result r1 r2

(* Evaluates ACSL extended quantifiers \max or \min: computes (an approximation
   of) the max (resp. the min) of [eval_term i] when [i] ranges between [min]
   and [max], according to [backward_left v1 v2] that reduces [v1] by assuming
   it is greater (resp. lower) than [v2]. *)
and eval_quantifier_extremum backward_left ~min ~max eval_term =
  (* Returns [r] as the result, with updated [empty] and [ldeps] fields. *)
  let return r =
    let ldeps = join_logic_deps r.ldeps (join_logic_deps min.ldeps max.ldeps) in
    let empty = min.empty || max.empty || r.empty in
    { r with ldeps; empty; }
  in
  let eval_ival ival = eval_term (Cvalue.V.inject_ival ival) in
  let project r = Cvalue.V.project_ival r.eover in
  match Ival.min_and_max (project min),
        Ival.min_and_max (project max) with
  | (min, Some b), (Some e, max) when Integer.le b e ->
    (* All integers between [b] and [e] are necessarily in the range to be
       considered. If [e-b] is small enough, evaluate [eval_term i] for each [i]
       between [b] and [e]. Otherwise, evaluate [eval_term] for the bound [b]
       and [e], and for the interval [b+1..e-1]. We could be more precise by
       subdividing the interval. *)
    let r =
      if Integer.equal e b
      then eval_term (Cvalue.V.inject_int b)
      else
        let fold =
          if Integer.(le (sub e b) (of_int 10))
          then Ival.fold_enum
          else Ival.fold_int_bounds
        in
        let ival = Ival.inject_range (Some b) (Some e) in
        let list = fold (fun i acc -> eval_ival i :: acc) ival [] in
        (* Reduce each result to only keep the possible extrema. *)
        let hd, tl = List.hd list, List.tl list in
        List.fold_left (compute_extremum backward_left) hd tl
    in
    (* Integers below [b] and above [e] may not be in the range to be considered,
       so we can't reduce [r] according to them. However, we must join to [r]
       the evaluation of [eval_term] for these integers, and we can reduce
       these evaluations to only keep results greater (or lower, according to
       [backward_left]) than [r]. *)
    let below = eval_ival (Ival.inject_range min (Some (Integer.pred b)))
    and above = eval_ival (Ival.inject_range (Some (Integer.succ e)) max) in
    let below = { below with eover = backward_left below.eover r.eover }
    and above = { above with eover = backward_left above.eover r.eover } in
    join_eval_result r (join_eval_result below above)
  | (b, _), (_, e) ->
    (* If [min] can be greater than [max], the result is unspecified. *)
    let r = eval_ival (Ival.inject_range b e) in
    return { r with eover = Cvalue.V.top; eunder = Cvalue.V.bottom }
  | exception Cvalue.V.Not_based_on_null ->
    let r = eval_term (Cvalue.V.join min.eover max.eover) in
    return { r with eover = Cvalue.V.top; eunder = Cvalue.V.bottom }

(* Introduces the logic variables [quantifiers] in [env], and reduces their
   value according to [predicate]. *)
and bind_comprehension_quantifiers env quantifiers predicate =
  let env = bind_logic_vars env quantifiers in
  match predicate with
  | None -> env
  | Some pred ->
    try
      let alarm_mode = Fail in
      let reduced_env = reduce_by_predicate ~alarm_mode env true pred in
      copy_logic_vars ~src:reduced_env ~dst:env quantifiers
    with LogicEvalError error ->
      display_evaluation_error ~loc:pred.pred_loc error;
      env

and eval_let_binding ~alarm_mode env logic_info =
  match logic_info with
  | { l_labels = []; l_tparams = []; l_profile = []; l_body = LBterm term } ->
    let var = logic_info.l_var_info in
    let env = bind_logic_vars env [var] in
    let var_term = Logic_const.tvar var in
    reduce_left_by_relation ~alarm_mode env true var_term Req term
  | _ -> unsupported "\\let binding"

(* -------------------------------------------------------------------------- *)
(* --- Evaluation of term lval and of terms as location                   --- *)
(* -------------------------------------------------------------------------- *)

and eval_tlhost ~alarm_mode env lv =
  match lv with
  | TVar lvar ->
    let base, typ =
      match lvar.lv_origin, Logic_utils.unroll_type lvar.lv_type with
      | Some v, _ -> Base.of_varinfo v, v.vtype
      | None, Ctype typ -> Base.of_c_logic_var lvar, typ
      | _ -> unsupported_lvar lvar
    in
    let loc = Location_Bits.inject base Ival.zero in
    { etype = typ;
      ldeps = empty_logic_deps;
      eover = loc;
      eunder = under_loc_from_over loc;
      empty = false; }
  | TResult typ ->
    (match env.result with
     | Some v ->
       let loc = Location_Bits.inject (Base.of_varinfo v) Ival.zero in
       { etype = typ;
         ldeps = empty_logic_deps;
         eunder = loc; eover = loc;
         empty = false; }
     | None -> no_result ())
  | TMem t ->
    let r = eval_term ~alarm_mode env t in
    let tres = match Cil.unrollTypeNode r.etype with
      | TPtr t -> t
      | _ -> ast_error "*p where p is not a pointer"
    in
    { etype = tres;
      ldeps = r.ldeps;
      eunder = loc_bytes_to_loc_bits r.eunder;
      eover = loc_bytes_to_loc_bits r.eover;
      empty = r.empty; }

and eval_toffset ~alarm_mode env typ toffset =
  match toffset with
  | TNoOffset ->
    { etype = typ;
      ldeps = empty_logic_deps;
      eunder = Ival.zero;
      eover = Ival.zero;
      empty = false; }
  | TIndex (idx, remaining) ->
    let typ_e, size = match Cil.unrollTypeNode typ with
      | TArray (t, size) -> t, size
      | _ -> ast_error "index on a non-array"
    in
    let idx = constraint_trange idx size in
    let idxs = eval_term ~alarm_mode env idx in
    let offsrem = eval_toffset ~alarm_mode env typ_e remaining in
    let size_e = Bit_utils.sizeof typ_e in
    let eover =
      let offset =
        try Cvalue.V.project_ival_bottom idxs.eover
        with Cvalue.V.Not_based_on_null -> Ival.top
      in
      let offset = Ival.scale_int_base size_e offset in
      Ival.add_int offset offsrem.eover
    in
    let eunder =
      let offset =
        try Cvalue.V.project_ival idxs.eunder
        with Cvalue.V.Not_based_on_null -> Ival.bottom
      in
      let offset = match size_e with
        | Int_Base.Top -> Ival.bottom
        (* Note: scale_int_base would overapproximate when given a
           Float.  Should never happen. *)
        | Int_Base.Value f ->
          assert (Ival.is_int offset);
          Ival.scale f offset
      in
      Ival.add_int_under offset offsrem.eunder
    in
    { etype = offsrem.etype;
      ldeps = join_logic_deps idxs.ldeps offsrem.ldeps;
      eunder; eover; empty = idxs.empty || offsrem.empty; }

  | TField (fi, remaining) ->
    let size_current default =
      try Ival.of_int (fst (Cil.fieldBitsOffset fi))
      with Cil.SizeOfError _ -> default
    in
    let attrs = Ast_attributes.filter_qualifier_attributes (Cil.typeAttrs typ) in
    let typ_fi = Cil.typeAddAttributes attrs fi.ftype in
    let offsrem = eval_toffset ~alarm_mode env typ_fi remaining in
    { etype = offsrem.etype;
      ldeps = offsrem.ldeps;
      eover = Ival.add_int (size_current Ival.top) offsrem.eover;
      eunder = Ival.add_int_under (size_current Ival.bottom) offsrem.eunder;
      empty = offsrem.empty; }

  | TModel _ -> unsupported "model fields"

and eval_tlval ~alarm_mode env (thost, toffs) =
  let rhost = eval_tlhost ~alarm_mode env thost in
  let roffset = eval_toffset ~alarm_mode env rhost.etype toffs in
  { etype = roffset.etype;
    ldeps = join_logic_deps rhost.ldeps roffset.ldeps;
    eunder = Location_Bits.shift_under roffset.eunder rhost.eunder;
    eover = Location_Bits.shift roffset.eover rhost.eover;
    empty = rhost.empty || roffset.empty;
  }

and eval_term_as_lval ~alarm_mode env t =
  match t.term_node with
  | TLval tlval ->
    eval_tlval ~alarm_mode env tlval
  | Tunion l ->
    let eunder, eover, deps, empty = List.fold_left
        (fun (accunder, accover, accdeps, accempty) t ->
           let r = eval_term_as_lval ~alarm_mode env t in
           Location_Bits.link accunder r.eunder,
           Location_Bits.join accover r.eover,
           join_logic_deps accdeps r.ldeps,
           accempty || r.empty
        ) (Location_Bits.top, Location_Bits.bottom, empty_logic_deps, false) l
    in
    { etype = infer_type t.term_type;
      ldeps = deps;
      eover; eunder; empty }
  | Tempty_set ->
    { etype = infer_type t.term_type;
      ldeps = empty_logic_deps;
      eunder = Location_Bits.bottom;
      eover = Location_Bits.bottom;
      empty = true }
  | Tat (t, lab) ->
    ignore (env_state env lab);
    eval_term_as_lval ~alarm_mode { env with e_cur = lab } t
  | TCast (true, _lt, t) ->
    (* Logic coerce on locations (that are pointers) can only introduce
       sets, that do not change the abstract value. *)
    eval_term_as_lval ~alarm_mode env t
  | Tif (tcond, ttrue, tfalse) ->
    eval_tif eval_term_as_lval Location_Bits.join Location_Bits.meet ~alarm_mode env
      tcond ttrue tfalse
  | Tcomprehension (term, quantifiers, predicate) ->
    let env = bind_comprehension_quantifiers env quantifiers predicate in
    let r = eval_term_as_lval ~alarm_mode env term in
    let pred_deps = Option.bind predicate (predicate_deps env) in
    let ldeps =
      Option.fold ~none:r.ldeps ~some:(join_logic_deps r.ldeps) pred_deps
    in
    { r with empty = true; ldeps }
  | Tinter _ -> unsupported "intersection of locations"
  | _ -> ast_error (Format.asprintf "non-lval term %a" Printer.pp_term t)

(* Evaluate a term as a non-empty under-approximated location, or raise
   [Not_an_exact_loc]. *)
and eval_term_as_exact_locs ~alarm_mode env t =
  match t.term_node with
  | TLval (TVar ({ lv_type = Linteger | Lreal } as logic_var), TNoOffset) ->
    Logic_var logic_var

  | TLval tlval ->
    let loc = eval_tlval ~alarm_mode env tlval in
    let typ = loc.etype in
    (* eval_term_as_exact_loc is only used for reducing values, and we must
       NOT reduce volatile locations. *)
    if Cil.typeHasQualifier "volatile" typ then raise Not_an_exact_loc;
    let loc = Locations.make_loc loc.eunder (Eval_typ.sizeof_lval_typ typ)in
    if Locations.is_bottom_loc loc then raise Not_an_exact_loc;
    Location (typ, loc)

  | TCast (true, Lreal, t) -> begin
      match eval_term_as_exact_locs ~alarm_mode env t with
      | Logic_var _ as x -> x
      | Location (_, locs) as r ->
        (* Real is not a supertype of non-finite floats because of NaN and
           infinities, we do not want to go in the case below. Instead,
           we check that there are no NaN/infinity, so that the subtyping
           relation indeed holds. *)
        let aux loc () =
          let state = env_current_state env in
          let v = find_or_alarm ~alarm_mode state loc in
          let v = Cvalue_forward.reinterpret Cil_const.longDoubleType v in
          let is_finite =
            match V.project_float v with
            | exception Cvalue.V.Not_based_on_null -> Unknown
            | f -> Fval.is_finite f
          in
          match is_finite with
          | True -> ()
          | False | Unknown -> raise Not_an_exact_loc
        in
        Eval_op.apply_on_all_locs aux locs ();
        r
    end

  | TCast (true, _, t) ->
    (* Otherwise it is always ok to pass through a TCast (true,_,_), as the destination
       type is always a supertype *)
    eval_term_as_exact_locs ~alarm_mode env t

  | TCast (false, ctype, t') ->
    pass_logic_cast Not_an_exact_loc ctype t';
    eval_term_as_exact_locs ~alarm_mode env t'

  | Tunion [t] ->
    eval_term_as_exact_locs ~alarm_mode env t

  | Tcomprehension (term, quantifiers, predicate) ->
    let env = bind_comprehension_quantifiers env quantifiers predicate in
    eval_term_as_exact_locs ~alarm_mode env term

  | _ -> raise Not_an_exact_loc

(* -------------------------------------------------------------------------- *)
(* --- Reduction by predicates                                            --- *)
(* -------------------------------------------------------------------------- *)

(* Apply [reduce] to the value of location [arg] if it is an exact location. *)
and reduce_exact_location ~alarm_mode env reduce loc =
  match eval_term_as_exact_locs ~alarm_mode env loc with
  | Logic_var logic_var ->
    let cvalue = LogicVarEnv.find logic_var env.logic_vars in
    let cvalue = reduce logic_var.lv_type cvalue in
    if V.is_bottom cvalue then raise Reduce_to_bottom;
    add_logic_var env logic_var cvalue
  | Location (typ_loc, locs) ->
    let aux loc env =
      let state = env_current_state env in
      let v = find_or_alarm ~alarm_mode state loc in
      let v = Cvalue_forward.reinterpret typ_loc v in
      let v' = reduce (Ctype typ_loc) v in
      if V.is_bottom v' then raise Reduce_to_bottom;
      if V.equal v' v then env else
        let state' = Cvalue.Model.reduce_previous_binding state loc v' in
        overwrite_current_state env state'
    in
    Eval_op.apply_on_all_locs aux locs env
  | exception Not_an_exact_loc
  | exception LogicEvalError _ -> env

and reduce_by_valid env positive access (tset: term) =
  let exception DoNotReduce in
  (* Auxiliary function that reduces \valid(lv+offs), where lv is atomic
     (no more tsets), and offs is a bits-expressed constant offset.
     [offs_typ] is supposed to be the type of the pointed location after [offs]
     has been applied; it can be different from [typeOf_pointed lv], for
     example if offset is a field access. *)
  let aux lv env (offs_typ, offs) =
    try
      if not (Location_Bits.cardinal_zero_or_one lv.eover) ||
         not (Ival.cardinal_zero_or_one offs)
      then raise DoNotReduce;
      let state = env_current_state env in
      let lvloc = make_loc lv.eover (Eval_typ.sizeof_lval_typ lv.etype) in
      (* [p] is the range that we attempt to reduce *)
      let alarm_mode = alarm_reduce_mode () in
      let p_orig = find_or_alarm ~alarm_mode state lvloc in
      let pb = Locations.loc_bytes_to_loc_bits p_orig in
      let shifted_p = Location_Bits.shift offs pb in
      let lshifted_p = make_loc shifted_p (Eval_typ.sizeof_lval_typ offs_typ) in
      let valid = (* reduce the shifted pointer to the wanted part *)
        if positive
        then Locations.valid_part access lshifted_p
        else Locations.invalid_part lshifted_p
      in
      let valid = valid.loc in
      if Location_Bits.equal shifted_p valid
      then env
      else
        (* Shift back *)
        let shift = Ival.neg_int offs in
        let pb = Location_Bits.shift shift valid in
        let p = Locations.loc_bits_to_loc_bytes pb in
        (* Store the result *)
        let state = Model.reduce_previous_binding state lvloc p in
        overwrite_current_state env state
    with
    | DoNotReduce | V.Not_based_on_null | Cil.SizeOfError _ | LogicEvalError _
      -> env
  in
  (*  Auxiliary function to reduce by the under-approximation of an offset.
      Since validities are contiguous, we simply reduce by the minimum and
      maximum of the under-approximation. *)
  let aux_min_max_offset f env off =
    try
      let env = match Ival.min_int off with
        | None -> env
        | Some min -> f env (Ival.inject_singleton min)
      in
      match Ival.max_int off with
      | None -> env
      | Some max -> f env (Ival.inject_singleton max)
    with Abstract_interp.Error_Bottom -> env
  in
  (* reduce [loc] so that its contents are a valid pointer to [typ] *)
  let aux_one_lval typ loc env =
    try
      let state =
        Eval_op.reduce_by_valid_loc ~positive access
          loc typ (env_current_state env)
      in
      overwrite_current_state env state
    with LogicEvalError _ -> env
  in
  (* reduce [t], which must be valid term-lval, so that its contents are
     a valid pointer to [typ]. If [typ] is not supplied, it is inferred
     from the type of [t]. *)
  let aux_lval ?typ tlval env =
    try
      let alarm_mode = alarm_reduce_mode () in
      let r = eval_tlval ~alarm_mode env tlval in
      let typ = match typ with None -> r.etype | Some t -> t in
      let loc = make_loc r.eunder (Eval_typ.sizeof_lval_typ typ) in
      let r = Eval_op.apply_on_all_locs (aux_one_lval typ) loc env in
      r
    with LogicEvalError _ -> env
  in
  let rec do_one env t =
    match t.term_node with
    | Tunion l ->
      List.fold_left do_one env l

    | TLval tlval -> aux_lval tlval env

    | TCast (false, Ctype typ, {term_node = TLval tlval}) -> aux_lval ~typ tlval env

    | TAddrOf (TMem {term_node = TLval tlval}, offs) ->
      (try
         let alarm_mode = alarm_reduce_mode () in
         let lt = eval_tlval ~alarm_mode env tlval in
         let typ = lt.etype in
         (* Compute the offsets, that depend on the type of the lval.
            The computed list is exactly what [aux] requires *)
         let roffs =
           eval_toffset ~alarm_mode env (Cil.typeOf_pointed typ) offs
         in
         let aux env offs = aux lt env (roffs.etype, offs) in
         aux_min_max_offset aux env roffs.eunder
       with LogicEvalError _ -> env)

    | TBinOp ((PlusPI | MinusPI) as op, {term_node = TLval tlval}, i) ->
      (try
         let alarm_mode = alarm_reduce_mode () in
         let rtlv = eval_tlval ~alarm_mode env tlval in
         let ri = eval_term ~alarm_mode env i in
         (* Convert offsets to a simpler form if [op] is [MinusPI] *)
         let li =
           try V.project_ival ri.eunder
           with V.Not_based_on_null -> raise Exit
         in
         let li = if op = PlusPI then li else Ival.neg_int li in
         let typ_p = Cil.typeOf_pointed rtlv.etype in
         let sbits = Integer.of_int (Cil.bitsSizeOf typ_p) in
         (* Compute the offsets expected by [aux], which are [i *
            8 * sizeof( *tlv)] *)
         let li = Ival.scale sbits li in
         (* Now reduce [tlv] by values possible for [i] *)
         let aux env offs = aux rtlv env (typ_p, offs) in
         aux_min_max_offset aux env li
       with
       | LogicEvalError _ | Exit -> env
      )
    | _ -> env
  in
  do_one env tset

(* Reduces the possible value of [arg] by assuming it points to a valid string
   (or not if [positive] is false), for reading or writing according to [access].
   This reduces the possible value of [arg] to a valid pointer (thus only
   considering the first character of the string), and filters out bases that
   cannot be a valid string because strlen returns bottom.
   This reduction could be improved by also reducing offsets according to the
   position of \0 in the pointed strings. *)
and reduce_by_valid_string ~alarm_mode env positive ~wide ~access arg =
  (* First, reduces [arg] assuming it is a valid pointer. *)
  let env = reduce_by_valid env positive access arg in
  (* Reduce the cvalue [v]:
     - if [positive] holds, remove bases which cannot be a valid string
       as the proper strlen builtin returns bottom;
     - if [positive] is false, remove bases which are a valid string,
       as the proper strlen builtin returns no alarm. *)
  let reduce _typ v =
    let wrapper =
      if wide
      then Builtins_string.frama_c_wcslen_wrapper
      else Builtins_string.frama_c_strlen_wrapper
    in
    let aux base offset acc =
      let value = Cvalue.V.inject base offset in
      let v, alarms = apply_logic_builtin wrapper env [value] in
      (* Beware of not removing const strings on the negation of \valid_string. *)
      let alarms = alarms || (access = Write && Base.is_read_only base) in
      if (positive && Cvalue.V.is_bottom v)
      || (not positive && not alarms)
      then acc
      else Cvalue.V.add base offset acc
    in
    Cvalue.V.fold_i aux v Cvalue.V.bottom
  in
  reduce_exact_location ~alarm_mode env reduce arg

(* Reduces [tl] so that [\base_addr(tl) == \base_addr(tr)] holds. *)
and reduce_left_by_base_addr_eq ~alarm_mode env tl tr =
  let right = eval_term ~alarm_mode env tr in
  let right_bases = Cvalue.V.get_bases right.eover in
  (* Avoids reducing the null base. *)
  let filter base = Base.is_null base || Base.SetLattice.mem base right_bases in
  let reduce _typ = Cvalue.V.filter_base filter in
  reduce_exact_location ~alarm_mode env reduce tl

(* reduce [tl] so that [rl rel tr] holds *)
and reduce_left_by_relation ~alarm_mode env positive tl rel tr =
  let rtl = eval_term ~alarm_mode env tr in
  let comp = Eva_utils.conv_relation rel in
  let reduce typ cvalue =
    Eval_op.backward_comp_left_from_type typ positive comp cvalue rtl.eover
  in
  reduce_exact_location ~alarm_mode env reduce tl

and reduce_by_relation ~alarm_mode env positive t1 rel t2 =
  (* special case: t1 is a term of the form "a rel' b",
     and is compared to "== 0" or "!= 0" => evaluate t1 directly;
     note: such terms may be created by other evaluation/reduction functions
     e.g. eval_predicate, reduce_by_predicate_content *)
  match t1.term_node, rel with
  | TBinOp (bop, t1', t2'), Rneq when is_rel_binop bop && Cil.isLogicZero t2 ->
    reduce_by_relation ~alarm_mode env positive t1' (rel_of_binop bop) t2'
  | TBinOp (bop, t1', t2'), Req when is_rel_binop bop && Cil.isLogicZero t2 ->
    reduce_by_relation ~alarm_mode env (not positive) t1' (rel_of_binop bop) t2'
  | _ ->
    (* Special case for \base_addr. *)
    let reduce_left env t1 rel t2 =
      match t1.term_node with
      | Tbase_addr (_lbl, t1) ->
        if rel = Req && positive || rel = Rneq && not positive
        then reduce_left_by_base_addr_eq ~alarm_mode env t1 t2
        else env
      | _ -> reduce_left_by_relation ~alarm_mode env positive t1 rel t2
    in
    let env = reduce_left env t1 rel t2 in
    let sym_rel = match rel with
      | Rgt -> Rlt | Rlt -> Rgt | Rle -> Rge | Rge -> Rle
      | Req -> Req | Rneq -> Rneq
    in
    reduce_left env t2 sym_rel t1

(* if you add something here, update [known_predicates] above also
   (and of course [eval_known_papp] below).
   May raise LogicEvalError or Not_an_exact_loc, when no reduction can be done,
   and Reduce_to_bottom, in which case the reduction leads to bottom. *)
and reduce_by_known_papp ~alarm_mode env positive li _labels args =
  (* If the term [arg] is a floating-point lvalue with an exact location,
     reduces its value in [env] by using the backward propagator on fval
     [fval_reduce]. *)
  let reduce_float fval_reduce arg =
    let reduce typ cvalue =
      match logic_type_fkind typ with
      | Some fkind -> begin
          try
            let v = Cvalue.V.project_float cvalue in
            let kind = Fval.kind fkind in
            match fval_reduce kind v with
            | `Value f -> V.inject_float f
            | `Bottom -> V.bottom
          with Cvalue.V.Not_based_on_null -> cvalue
        end
      | None -> cvalue (* Better safe than sorry, we may have e.g. an int
                          location here *)
    in
    reduce_exact_location ~alarm_mode env reduce arg
  in
  (* Reduces [f] to positive or negative infinity (according to [pos]),
     or to the complement if [positive] is false. *)
  let reduce_by_infinity ~pos prec f =
    let inf = if pos then Fval.pos_infinity prec else Fval.neg_infinity prec in
    let fval =
      if positive
      then inf
      else Fval.(join nan (join (Fval.neg inf) (top_finite prec)))
    in
    Fval.narrow fval f
  in
  match li.l_var_info.lv_name, args with
  | "\\is_finite", [arg] ->
    reduce_float (Fval.backward_is_finite ~positive) arg
  | "\\is_infinite", [arg] ->
    reduce_float (Fval.backward_is_infinite ~positive) arg
  | "\\is_plus_infinity", [arg] ->
    reduce_float (reduce_by_infinity ~pos:true) arg
  | "\\is_minus_infinity", [arg] ->
    reduce_float (reduce_by_infinity ~pos:false) arg
  | "\\is_NaN", [arg] ->
    reduce_float (fun _fkind -> Fval.backward_is_nan ~positive) arg
  | ("\\eq_float" | "\\eq_double"), [t1;t2] ->
    reduce_by_relation ~alarm_mode env positive t1 Req t2
  | ("\\ne_float" | "\\ne_double"), [t1;t2] ->
    reduce_by_relation ~alarm_mode env positive t1 Rneq t2
  | ("\\lt_float" | "\\lt_double"), [t1;t2] ->
    reduce_by_relation ~alarm_mode env positive t1 Rlt t2
  | ("\\le_float" | "\\le_double"), [t1;t2] ->
    reduce_by_relation ~alarm_mode env positive t1 Rle t2
  | ("\\gt_float" | "\\gt_double"), [t1;t2] ->
    reduce_by_relation ~alarm_mode env positive t1 Rgt t2
  | ("\\ge_float" | "\\ge_double"), [t1;t2] ->
    reduce_by_relation ~alarm_mode env positive t1 Rge t2
  | "\\subset", [argl;argr] when positive ->
    let vr = (eval_term ~alarm_mode env argr).eover in
    let reduce _typ vl = Cvalue.V.narrow vl vr in
    reduce_exact_location ~alarm_mode env reduce argl
  | "valid_read_string", [arg] ->
    reduce_by_valid_string ~alarm_mode env positive ~wide:false ~access:Read arg
  | "valid_string", [arg] ->
    reduce_by_valid_string ~alarm_mode env positive ~wide:false ~access:Write arg
  | "valid_read_wstring", [arg] ->
    reduce_by_valid_string ~alarm_mode env positive ~wide:true ~access:Read arg
  | "valid_wstring", [arg] ->
    reduce_by_valid_string ~alarm_mode env positive ~wide:true ~access:Write arg

  | _ -> (* Do not fail here. We can be asked to reduce on predicates that we
            can evaluate, but on which we are not able to reduce on (yet ?).*)
    env

(** Big recursive functions for predicates *)

and reduce_by_predicate ~alarm_mode env positive p =
  let loc = p.pred_loc in
  let rec reduce_by_predicate_content env positive p_content =
    match positive,p_content with
    | true,Ptrue | false,Pfalse -> env

    | true,Pfalse | false,Ptrue ->
      overwrite_current_state env Cvalue.Model.bottom

    (* desugared form of a <= b <= c <= d *)
    | true, Pand (
        {pred_content=Pand (
             {pred_content=Prel ((Rlt | Rgt | Rle | Rge | Req as op),_ta,tb) as p1},
             {pred_content=Prel (op', tb',tc) as p2})},
        {pred_content=Prel (op'',tc',_td) as p3})
      when
        op = op' && op' = op'' &&
        is_same_term_coerce tb tb' &&
        is_same_term_coerce tc tc'
      ->
      let red env p = reduce_by_predicate_content env positive p in
      let env = red env p1 in
      let env = red env p3 in
      let env = red env p2 in
      (*Not really useful in practice*)
      (*let env = red env (Prel (op, ta, tc)) in
        let env = red env (Prel (op, tb, td)) in *)
      env

    | true,Pand (p1,p2) | false,Por(p1,p2)->
      let r1 = reduce_by_predicate ~alarm_mode env positive p1 in
      reduce_by_predicate ~alarm_mode r1 positive p2

    | true,Por (p1,p2 ) | false,Pand (p1, p2) ->
      let env1 = reduce_by_predicate ~alarm_mode env positive p1 in
      let env2 = reduce_by_predicate ~alarm_mode env positive p2 in
      join_env env1 env2

    | true,Pimplies (p1,p2) ->
      let env1 = reduce_by_predicate ~alarm_mode env false p1 in
      let env2 = reduce_by_predicate ~alarm_mode env true p2 in
      join_env env1 env2

    | false,Pimplies (p1,p2) ->
      reduce_by_predicate ~alarm_mode
        (reduce_by_predicate ~alarm_mode env true p1)
        false
        p2

    | _,Pnot p -> reduce_by_predicate ~alarm_mode env (not positive) p

    | true,Piff (p1, p2) ->
      let red1 = reduce_by_predicate_content env true (Pand (p1, p2)) in
      let red2 = reduce_by_predicate_content env false (Por (p1, p2)) in
      join_env red1 red2

    | false,Piff (p1, p2) ->
      reduce_by_predicate ~alarm_mode env true
        (Logic_const.por ~loc
           (Logic_const.pand ~loc (p1, Logic_const.pnot ~loc p2),
            Logic_const.pand ~loc (Logic_const.pnot ~loc p1, p2)))

    | _,Pxor(p1,p2) ->
      reduce_by_predicate ~alarm_mode env
        (not positive) (Logic_const.piff ~loc (p1, p2))

    | _,Prel (op,t1,t2) ->
      begin
        try
          (* ugly, but eval_predicate_content does not exist yet *)
          let p = Logic_const.unamed ~loc p_content in
          let p' = if positive then p else Logic_const.pnot ~loc p in
          (* Evaluate the predicate before reducing. In some cases, although
             evaluation results in Bottom, reduction fails to reduce the
             resulting env to Bottom, and we lose precision. *)
          match eval_predicate env p' with
          | True -> env
          | False -> overwrite_current_state env Cvalue.Model.bottom
          | Unknown -> reduce_by_relation ~alarm_mode env positive t1 op t2
        with
        | LogicEvalError _ -> env
        | Reduce_to_bottom ->
          overwrite_current_state env Cvalue.Model.bottom
          (* if the exception was obtained without an alarm emitted,
             it is correct to return the bottom state *)
      end

    | _,Pvalid (_label,tsets) ->
      (* TODO: label should not be ignored. Instead, we should clear
         variables that are not in scope at the label. *)
      reduce_by_valid env positive Write tsets
    | _,Pvalid_read (_label,tsets) ->
      reduce_by_valid env positive Read tsets
    | _,Pobject_pointer (_label, tsets) ->
      reduce_by_valid env positive No_access tsets

    | _,Pvalid_function _tsets -> env (* TODO *)

    | _,(Pinitialized (lbl_initialized,tsets)
        | Pdangling (lbl_initialized,tsets)) ->
      begin
        try
          let alarm_mode = alarm_reduce_mode () in
          (* See comments in the code for the evaluation of Pinitialized *)
          let star_tsets = deref_tsets tsets in
          let rlocb = eval_tlval ~alarm_mode env star_tsets in
          (* No reduction on negations of \initialized or \dangling on multiple
             locations: at least one of them is non initialized/dangling, but
             which one? Reduction would only be possible in the rare case where
             only one of the locations might be non initialized/dangling. *)
          if not (positive || Location_Bits.cardinal_zero_or_one rlocb.eover)
          then env
          else
            let size = Eval_typ.sizeof_lval_typ rlocb.etype in
            let state = env_state env lbl_initialized in
            let fred = match p_content with
              | Pinitialized _ -> V_Or_Uninitialized.reduce_by_initializedness
              | Pdangling _ -> V_Or_Uninitialized.reduce_by_danglingness
              | _ -> assert false
            in
            let fred = Eval_op.reduce_by_initialized_defined (fred positive) in
            let state_reduced =
              let loc = make_loc rlocb.eunder size in
              let loc = Eval_op.make_loc_contiguous loc in
              Eval_op.apply_on_all_locs fred loc state
            in
            overwrite_state env state_reduced lbl_initialized
        with LogicEvalError _ -> env
      end

    | _,Pat (p, lbl) ->
      (try
         let env_at = { env with e_cur = lbl } in
         let env' = reduce_by_predicate ~alarm_mode env_at positive p in
         { env' with e_cur = env.e_cur }
       with LogicEvalError _ -> env)

    | true, Pforall (varl, p) | false, Pexists (varl, p) ->
      begin
        try
          (* TODO: add case analysis on the variables of the quantification
             that are constrained *)
          let env = bind_logic_vars env varl in
          let env_result = reduce_by_predicate ~alarm_mode env true p in
          unbind_logic_vars env_result varl
        with LogicEvalError _ -> env
      end

    | _, Plet (li, p') ->
      begin
        try
          let env = eval_let_binding ~alarm_mode env li in
          let env_result = reduce_by_predicate ~alarm_mode env positive p' in
          unbind_logic_vars env_result [li.l_var_info]
        with LogicEvalError _ -> env
      end

    | _,Papp (li, labels, args) -> begin
        if is_known_predicate li.l_var_info then
          try reduce_by_known_papp ~alarm_mode env positive li labels args
          with
          | Reduce_to_bottom -> overwrite_current_state env Model.bottom
          | LogicEvalError _ | Not_an_exact_loc -> env
        else
          match Inline.inline_predicate ~inline ~current:env.e_cur p with
          | None -> env
          | Some p' -> reduce_by_predicate_content env positive p'.pred_content
      end
    | _,Pif (tcond, ptrue, pfalse) ->
      begin
        let reduce = reduce_by_predicate ~alarm_mode in
        let r = eval_term ~alarm_mode env tcond in
        let ctrue = Cvalue.V.contains_non_zero r.eover in
        let cfalse = Cvalue.V.contains_zero r.eover in
        match ctrue, cfalse with
        | true, true ->
          let reduce_by_rel =
            reduce_by_relation ~alarm_mode env positive tcond
          in
          let env_true = reduce_by_rel Cil_types.Rneq (Cil.lzero ()) in
          let env_false = reduce_by_rel Cil_types.Req (Cil.lzero ()) in
          let env_true =  reduce env_true positive ptrue in
          let env_false = reduce env_false positive pfalse in
          join_env env_true env_false
        | true, false -> reduce env positive ptrue
        | false, true -> reduce env positive pfalse
        | false, false -> assert false (* a logic alarm would have been raised*)
      end
    | true, Pexists (_, _) | false, Pforall (_, _)
    | _,Pallocable (_,_) | _,Pfreeable (_,_) | _,Pfresh (_,_,_,_)
    | _, Pseparated _
      -> env
  in
  reduce_by_predicate_content env positive p.pred_content

(* -------------------------------------------------------------------------- *)
(* --- Evaluation of predicates                                           --- *)
(* -------------------------------------------------------------------------- *)

and eval_predicate env pred =
  let alarm_mode = Fail in
  let loc = pred.pred_loc in
  let rec do_eval env p =
    try do_eval_exn env p
    with LogicEvalError ee ->
      display_evaluation_error ~loc:p.pred_loc ee;
      Unknown
  and do_eval_exn env p =
    match p.pred_content with
    | Ptrue -> True
    | Pfalse -> False
    | Pand (p1,p2 ) ->
      begin match do_eval env p1 with
        | True -> do_eval env p2
        | False -> False
        | Unknown ->
          let reduced = reduce_by_predicate ~alarm_mode env true p1 in
          match do_eval reduced p2 with
          | False -> False
          | _ -> Unknown
      end
    | Por (p1,p2 ) ->
      let val_p1 = do_eval env p1 in
      (*Format.printf "Disjunction: state %a p1:%a@."
          Cvalue.Model.pretty (env_current_state env)
          Printer.pp_predicate p1; *)
      begin match val_p1 with
        | True -> True
        | False -> do_eval env p2
        | Unknown -> begin
            let reduced_state = reduce_by_predicate ~alarm_mode env false p1 in
            (* Format.printf "Disjunction: reduced to %a to eval %a@."
               Cvalue.Model.pretty (env_current_state reduced_state)
               Printer.pp_predicate p2; *)
            match do_eval reduced_state p2 with
            | True -> True
            | _ -> Unknown
          end
      end
    | Pxor (p1,p2) ->
      begin match do_eval env p1, do_eval env p2 with
        | True, True -> False
        | False, False -> False
        | True, False | False, True -> True
        | Unknown, _ | _, Unknown -> Unknown
      end
    | Piff (p1,p2 ) ->
      begin match do_eval env p1,do_eval env p2 with
        | True, True | False, False ->  True
        | Unknown, _ | _, Unknown -> Unknown
        | _ -> False
      end
    | Pat (p, lbl) ->
      ignore (env_state env lbl);
      do_eval { env with e_cur = lbl } p

    | Pvalid (_, tsets)
    | Pvalid_read (_, tsets)
    | Pobject_pointer (_, tsets) ->
      (* TODO: see same constructor in reduce_by_predicate *)
      let kind =
        match p.pred_content with
        | Pvalid_read _ -> Read
        | Pvalid _ -> Write
        | Pobject_pointer _ -> No_access
        | _ -> assert false
      in
      let typ_pointed = Logic_typing.ctype_of_pointed tsets.term_type in
      (* Check if we are trying to write in a const l-value *)
      if kind = Write && Eva_utils.is_const_write_invalid typ_pointed
      then False
      else
        let eover, eunder, indeterminate, empty =
          match tsets.term_node with
          | TLval tlval ->
            (* Do not use [eval_term]: the evaluation would fail if the value of
               [tlval] is uninitialized or escaping. *)
            let r = eval_tlval ~alarm_mode env tlval in
            let loc = make_loc r.eover (Eval_typ.sizeof_lval_typ r.etype) in
            let state = env_current_state env in
            let v = find_indeterminate ~alarm_mode state loc in
            let v, indeterminate =
              Cvalue.V_Or_Uninitialized.(get_v v, is_indeterminate v)
            in
            v, Cvalue.V.bottom, indeterminate, r.empty
          | _ ->
            let result = eval_term ~alarm_mode env tsets in
            result.eover, result.eunder, false, result.empty
        in
        let status =
          (* [True] on empty sets, [False] on bottom locations otherwise. *)
          if Cvalue.V.is_bottom eover
          then if empty then True else False
          else
            let size = Eval_typ.sizeof_lval_typ typ_pointed in
            let make_loc l = make_loc (loc_bytes_to_loc_bits l) size in
            let loc_over = make_loc eover in
            (* The predicate holds if [eover] is entirely valid. It is false if
               [eover] is entirely invalid or if [eunder] contains an invalid
               location. Unknown otherwise. *)
            if Locations.is_valid kind loc_over
            then True
            else if Locations.is_bottom_loc (valid_part kind loc_over)
                 || contains_invalid_loc kind (make_loc eunder)
            then False
            else Unknown
        in
        (* False status on uninitialized or escaping pointers. *)
        let status =
          if indeterminate then join_predicate_status status False else status
        in
        (* True status on empty sets. *)
        if empty then join_predicate_status status True else status

    | Pvalid_function tsets -> begin
        let v = eval_term ~alarm_mode env tsets in
        let funs, warn = Main_values.CVal.resolve_functions v.eover in
        match funs with
        | `Top -> Unknown
        | `Value funs ->
          let typ = Cil.typeOf_pointed v.etype in
          let funs, warn' = Eval_typ.compatible_functions typ funs in
          if warn || warn' then
            (* No function possible -> signal hard error. Otherwise, follow
               Eva's convention, which is not to stop on semi-ok functions. *)
            if funs = [] then False else Unknown
          else
            True
      end

    | Pinitialized (label,tsets) | Pdangling (label,tsets) -> begin
        (* Create [*tsets] and compute its location. This is important in
           case [tsets] points to the address of a bitfield, which we
           cannot evaluate as a pointer (indexed on bytes) *)
        let star_tsets = deref_tsets tsets in
        let locb = eval_tlval ~alarm_mode env star_tsets in
        let state = env_state env label in
        match p.pred_content with
        | Pinitialized _ -> eval_initialized state locb
        | Pdangling _ -> eval_dangling state locb
        | _ -> assert false
      end

    | Prel (op,t1,t2) ->
      let r = eval_binop ~alarm_mode env (lop_to_cop op) t1 t2 in
      (* [eval_binop] uses the forward semantics of [Cvalue.V], which does not
         handle empty sets. Returns [Unknown] if empty sets are possible. *)
      if r.empty
      then Unknown
      else if V.equal V.singleton_zero r.eover
      then False
      else if V.equal V.singleton_one r.eover
      then True
      else Unknown

    | Pforall (varl, p') | Pexists (varl, p') ->
      begin
        (* If [p'] is true (or false) for all possible values of [varl],
           then so is Pforall(varl, p') and Pexists(varl, p'). *)
        let env = bind_logic_vars env varl in
        let r = do_eval env p' in
        if r <> Unknown then r else
          (* Otherwise:
             - if [p'] evaluates to [false] for at least some values of [varl],
               then Pforall (varl, p') is false.
             - if [p'] evaluates to [true] for at least some values of [varl],
               then Pexists (varl, p') is true.

             In order to find such values, we reduce the environment by assuming
             [p'] is true (for Pexists) or false (for Pforall), and then we
             reevaluate [p'] with these values. *)
          let positive =
            match p.pred_content with Pforall _ -> false | _ -> true
          in
          let reduced_env = reduce_by_predicate ~alarm_mode env positive p' in
          (* Reduce the values of logical variables [varl] in [env] according to
             [reduced_env]. To be more precise, we could reduce them to
             singleton values — for instance by using the interval bounds. *)
          let env = copy_logic_vars ~src:reduced_env ~dst:env varl in
          match p.pred_content, do_eval env p' with
          | Pexists _, True -> True
          | Pforall _, False -> False
          | _ -> Unknown
      end

    | Plet (li, p') ->
      let env = eval_let_binding ~alarm_mode env li in
      do_eval env p'

    | Pnot p ->  begin match do_eval env p with
        | True -> False
        | False -> True
        | Unknown -> Unknown
      end

    | Pimplies (p1,p2) ->
      do_eval env (Logic_const.por ~loc ((Logic_const.pnot ~loc p1), p2))

    | Pseparated ltsets ->
      (try
         let to_zones tset =
           (* Create [*tset] and compute its location. This is important in
              case [tset] points to the address of a bitfield, which we
              cannot evaluate as a pointer (indexed on bytes). *)
           let star_tset = deref_tsets tset in
           let rtset = eval_tlval ~alarm_mode env star_tset in
           let size = Eval_typ.sizeof_lval_typ rtset.etype in
           let loc_over = rtset.eover in
           let loc_under = rtset.eunder in
           Locations.enumerate_bits (Locations.make_loc loc_over size),
           Locations.enumerate_bits_under (Locations.make_loc loc_under size)
         in
         let lz = List.map to_zones ltsets in
         let unknown = ref false in
         (* Are those two lists of locations separated? *)
         let do_two (z1, zu1) l2 =
           let combine (z2, zu2) =
             if Zone.intersects z1 z2 then begin
               unknown := true;
               if Zone.intersects zu1 zu2 then raise Exit;
             end
           in
           List.iter combine l2
         in
         let rec aux = function
           | [] | [_] -> ()
           | loc :: qlocs ->
             do_two loc qlocs;
             aux qlocs
         in
         aux lz;
         if !unknown then Unknown else True
       with
       | Exit -> False)

    | Papp (li, labels, args) -> begin
        if is_known_predicate li.l_var_info then
          eval_known_papp env li labels args
        else
          match Inline.inline_predicate ~inline ~current:env.e_cur p with
          | None -> Unknown
          | Some p' -> do_eval env p'
      end

    | Pif (tcond, ptrue, pfalse) ->
      begin
        let r = eval_term ~alarm_mode env tcond in
        let ctrue =  Cvalue.V.contains_non_zero r.eover
        and cfalse =  Cvalue.V.contains_zero r.eover in
        match ctrue, cfalse with
        | true, true ->
          let reduce_by_rel = reduce_by_relation ~alarm_mode env true tcond in
          let env_true = reduce_by_rel Cil_types.Rneq (Cil.lzero ()) in
          let env_false = reduce_by_rel Cil_types.Req (Cil.lzero ()) in
          join_predicate_status (do_eval env_true ptrue) (do_eval env_false pfalse)
        | true, false -> do_eval env ptrue
        | false, true -> do_eval env pfalse
        | false, false -> assert false (* a logic alarm would have been raised*)
      end
    | Pfreeable (BuiltinLabel Here, t) ->
      let r = eval_term ~alarm_mode env t in
      Builtins_malloc.freeable r.eover
    | Pfresh _ | Pallocable _ | Pfreeable _ -> Unknown

  (* Logic predicates. Update the list known_predicates above if you
     add something here. *)
  and eval_known_papp env li _labels args =
    let open Abstract_interp in
    let unary_float unary_fun arg =
      try
        let eval_result = eval_term ~alarm_mode env arg in
        unary_fun (V.project_float eval_result.eover)
      with
      | V.Not_based_on_null -> Unknown
    in
    let fval_cmp comp arg1 arg2 =
      try
        let e1 = eval_term ~alarm_mode env arg1
        and e2 = eval_term ~alarm_mode env arg2 in
        let f1 = V.project_float e1.eover
        and f2 = V.project_float e2.eover in
        Fval.forward_comp comp f1 f2
      with
      | V.Not_based_on_null -> Unknown
    in
    match li.l_var_info.lv_name, args with
    | "\\is_finite", [arg] -> unary_float Fval.is_finite arg
    | "\\is_infinite", [arg] -> unary_float Fval.is_infinite arg
    | "\\is_plus_infinity", [arg] ->
      let pos_inf = Fval.pos_infinity Float_sig.Single in
      unary_float (fun f -> Fval.forward_comp Comp.Eq f pos_inf) arg
    | "\\is_minus_infinity", [arg] ->
      let neg_inf = Fval.neg_infinity Float_sig.Single in
      unary_float (fun f -> Fval.forward_comp Comp.Eq f neg_inf) arg
    | "\\is_NaN", [arg] -> inv_truth (unary_float Fval.is_not_nan arg)
    | ("\\eq_float" | "\\eq_double"), [arg1;arg2] -> fval_cmp Comp.Eq arg1 arg2
    | ("\\ne_float" | "\\ne_double"), [arg1;arg2] -> fval_cmp Comp.Ne arg1 arg2
    | ("\\lt_float" | "\\lt_double"), [arg1;arg2] -> fval_cmp Comp.Lt arg1 arg2
    | ("\\le_float" | "\\le_double"), [arg1;arg2] -> fval_cmp Comp.Le arg1 arg2
    | ("\\gt_float" | "\\gt_double"), [arg1;arg2] -> fval_cmp Comp.Gt arg1 arg2
    | ("\\ge_float" | "\\ge_double"), [arg1;arg2] -> fval_cmp Comp.Ge arg1 arg2
    | "\\warning", _ -> begin
        match args with
        | [{ term_node = TConst(LStr(str))}] ->
          Self.warning "reached \\warning(\"%s\")" str; Unknown
        | _ ->
          Self.abort
            "Wrong argument: \\warning expects a constant string"
      end
    | "\\subset", [argl;argr] ->
      let l = eval_term ~alarm_mode env argl in
      let r = eval_term ~alarm_mode env argr in
      if r.empty then
        if V.is_bottom l.eover then True
        else if not (V.is_bottom l.eunder || l.empty) then False
        else Unknown
      else if V.is_included l.eover r.eunder then
        True (* all elements of [l] are included in the guaranteed elements
                of [r] *)
      else if not (V.is_included l.eunder r.eover) ||
              not (V.intersects l.eover r.eover)
      then False (* one guaranteed element of [l] is not included in [r],
                    or [l] and [r] are disjoint, in which case there is
                    an element of [l] not in [r]. (Here, [l] is not bottom,
                    as [V.is_included bottom r.eunder] holds. *)
      else Unknown
    | "\\tainted", [_] -> Unknown
    | "valid_read_string", [arg] ->
      let r = eval_term ~alarm_mode env arg in
      eval_valid_read_str ~wide:false env r.eover
    | "valid_string", [arg] ->
      let r = eval_term ~alarm_mode env arg in
      eval_valid_str ~wide:false env r.eover
    | "valid_read_wstring", [arg] ->
      let r = eval_term ~alarm_mode env arg in
      eval_valid_read_str ~wide:true env r.eover
    | "valid_wstring", [arg] ->
      let r = eval_term ~alarm_mode env arg in
      eval_valid_str ~wide:true env r.eover
    | "is_allocable", [arg] when comes_from_fc_stdlib li.l_var_info ->
      let r = eval_term ~alarm_mode env arg in
      eval_is_allocable r.eover
    | _, _ -> assert false
  in
  do_eval env pred

(* -------------------------------------------------------------------------- *)
(* --- Dependencies of predicates                                         --- *)
(* -------------------------------------------------------------------------- *)

and eval_tsets_deps ~alarm_mode env lbl tsets =
  let star_tsets = deref_tsets tsets in
  let r = eval_tlval ~alarm_mode env star_tsets in
  let size_bits = Eval_typ.sizeof_lval_typ r.etype in
  let loc = make_loc r.eover size_bits in
  let zone = enumerate_valid_bits Locations.Read loc in
  Logic_label.Map.add lbl zone r.ldeps

and predicate_deps env pred =
  let alarm_mode = Ignore in
  let rec do_eval env p =
    let term_deps term = (eval_term ~alarm_mode env term).ldeps in
    let tsets_deps lbl tsets = eval_tsets_deps ~alarm_mode env lbl tsets in
    let logic_info_deps li =
      match li.l_body with
      | LBnone -> empty_logic_deps
      | LBterm t -> term_deps t
      | LBpred p -> do_eval env p
      | _ -> unsupported (Format.asprintf "%a" Printer.pp_logic_info li)
    in
    match p.pred_content with
    | Ptrue | Pfalse -> empty_logic_deps

    | Pand (p1, p2) | Por (p1, p2 ) | Pxor (p1, p2) | Piff (p1, p2 )
    | Pimplies (p1, p2) ->
      join_logic_deps (do_eval env p1) (do_eval env p2)

    | Prel (_, t1, t2) ->
      join_logic_deps (term_deps t1) (term_deps t2)

    | Pif (c, p1, p2) ->
      join_logic_deps (term_deps c)
        (join_logic_deps (do_eval env p1) (do_eval env p2))

    | Pat (p, lbl) ->
      do_eval { env with e_cur = lbl } p

    | Pvalid (_, tsets) | Pvalid_read (_, tsets)
    | Pobject_pointer (_, tsets) | Pvalid_function tsets ->
      term_deps tsets

    | Pinitialized (lbl, tsets) | Pdangling (lbl, tsets) ->
      tsets_deps lbl tsets

    | Pnot p -> do_eval env p

    | Pseparated ltsets ->
      List.fold_left
        (fun acc tsets -> join_logic_deps acc (tsets_deps lbl_here tsets))
        empty_logic_deps ltsets

    | Pexists (l, p) | Pforall (l, p) ->
      let env = bind_logic_vars env l in
      (* TODO: unbind all references to l in the results? If so, clean up
         Logic_interp.do_term_lval. *)
      do_eval env p

    | Plet (li, p) ->
      let env = eval_let_binding ~alarm_mode env li in
      join_logic_deps (logic_info_deps li) (do_eval env p)

    | Papp (li, _labels, args) -> begin
        if is_known_predicate li.l_var_info then
          List.fold_left
            (fun acc arg -> join_logic_deps acc (term_deps arg))
            empty_logic_deps args
        else
          match Inline.inline_predicate ~inline ~current:env.e_cur p with
          | None -> unsupported (Format.asprintf "%a" Printer.pp_predicate p)
          | Some p' -> do_eval env p'
      end

    | Pfresh _ | Pallocable _ | Pfreeable _
      -> unsupported (Format.asprintf "%a" Printer.pp_predicate p)
  in
  try Some (do_eval env pred)
  with LogicEvalError _ -> None

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

(* Position default value for ~alarm_mode *)
let reduce_by_predicate env positive p =
  let alarm_mode = alarm_reduce_mode () in
  reduce_by_predicate ~alarm_mode env positive p

let eval_tlval_as_location ~alarm_mode env t =
  let r = eval_term_as_lval ~alarm_mode env t in
  let s = Eval_typ.sizeof_lval_typ r.etype in
  make_loc r.eover s

(* Return a pair of (under-approximating, over-approximating) zones. *)
let eval_tlval_as_zone_under_over ~alarm_mode access env t =
  let r = eval_term_as_lval ~alarm_mode env t in
  let s = Eval_typ.sizeof_lval_typ r.etype in
  let under = enumerate_valid_bits_under access (make_loc r.eunder s) in
  let over = enumerate_valid_bits access (make_loc r.eover s) in
  (under, over)

let eval_tlval_as_zone ~alarm_mode access env t =
  let _under, over =
    eval_tlval_as_zone_under_over ~alarm_mode access env t
  in
  over

let tlval_deps env t = (eval_term_as_lval ~alarm_mode:Ignore env t).ldeps