(* * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. *) open! IStd open! AbstractDomain.Types module F = Format module L = Logging exception Not_One_Symbol open Ints type sign = Plus | Minus [@@deriving compare] module Sign = struct type t = sign [@@deriving compare] let neg = function Plus -> Minus | Minus -> Plus let eval_big_int x i1 i2 = match x with Plus -> Z.(i1 + i2) | Minus -> Z.(i1 - i2) let eval_neg_if_minus x i = match x with Plus -> i | Minus -> Z.neg i let pp ~need_plus : F.formatter -> t -> unit = fun fmt -> function | Plus -> if need_plus then F.pp_print_char fmt '+' | Minus -> F.pp_print_char fmt '-' end module SymLinear = struct module M = Symb.SymbolMap (** Map from symbols to integer coefficients. [{ x -> 2, y -> 5 }] represents the value [2 * x + 5 * y] *) type t = NonZeroInt.t M.t [@@deriving compare] let empty : t = M.empty let is_empty : t -> bool = fun x -> M.is_empty x let singleton_one : Symb.Symbol.t -> t = fun s -> M.singleton s NonZeroInt.one let singleton_minus_one : Symb.Symbol.t -> t = fun s -> M.singleton s NonZeroInt.minus_one let is_le_zero : t -> bool = fun x -> M.for_all (fun s v -> Symb.Symbol.is_unsigned s && NonZeroInt.is_negative v) x let is_ge_zero : t -> bool = fun x -> M.for_all (fun s v -> Symb.Symbol.is_unsigned s && NonZeroInt.is_positive v) x let le : t -> t -> bool = fun x y -> phys_equal x y || let le_one_pair s v1_opt v2_opt = let v1 = NonZeroInt.opt_to_big_int v1_opt in let v2 = NonZeroInt.opt_to_big_int v2_opt in Z.(equal v1 v2) || (Symb.Symbol.is_unsigned s && Z.leq v1 v2) in M.for_all2 ~f:le_one_pair x y let pp1 : markup:bool -> is_beginning:bool -> F.formatter -> Symb.Symbol.t -> NonZeroInt.t -> unit = fun ~markup ~is_beginning f s c -> let c = (c :> Z.t) in let c = if is_beginning then c else if Z.gt c Z.zero then ( F.pp_print_string f " + " ; c ) else ( F.pp_print_string f " - " ; Z.neg c ) in if Z.(equal c one) then (Symb.Symbol.pp_mark ~markup) f s else if Z.(equal c minus_one) then F.fprintf f "-%a" (Symb.Symbol.pp_mark ~markup) s else F.fprintf f "%a%s%a" Z.pp_print c SpecialChars.dot_operator (Symb.Symbol.pp_mark ~markup) s let pp : markup:bool -> is_beginning:bool -> F.formatter -> t -> unit = fun ~markup ~is_beginning f x -> if M.is_empty x then if is_beginning then F.pp_print_string f "0" else () else ( M.fold (fun s c is_beginning -> pp1 ~markup ~is_beginning f s c ; false ) x is_beginning : bool ) |> ignore let zero : t = M.empty let is_zero : t -> bool = M.is_empty let neg : t -> t = fun x -> M.map NonZeroInt.( ~- ) x let remove_positive_length_symbol : t -> t = M.filter (fun symb coeff -> let path = Symb.Symbol.path symb in not (NonZeroInt.is_positive coeff && Symb.SymbolPath.is_length path) ) let plus : t -> t -> t = fun x y -> let plus_coeff _ c1 c2 = NonZeroInt.plus c1 c2 in PhysEqual.optim2 x y ~res:(M.union plus_coeff x y) let mult_const : NonZeroInt.t -> t -> t = fun n x -> if NonZeroInt.is_one n then x else M.map (NonZeroInt.( * ) n) x let exact_div_const_exn : t -> NonZeroInt.t -> t = fun x n -> if NonZeroInt.is_one n then x else M.map (fun c -> NonZeroInt.exact_div_exn c n) x (* Returns a symbol when the map contains only one symbol s with a given coefficient. *) let one_symbol_of_coeff : NonZeroInt.t -> t -> Symb.Symbol.t option = fun coeff x -> match M.is_singleton_or_more x with | IContainer.Singleton (k, v) when Z.equal (v :> Z.t) (coeff :> Z.t) -> Some k | _ -> None let fold m ~init ~f = let f s coeff acc = f acc s coeff in M.fold f m init let get_one_symbol_opt : t -> Symb.Symbol.t option = one_symbol_of_coeff NonZeroInt.one let get_mone_symbol_opt : t -> Symb.Symbol.t option = one_symbol_of_coeff NonZeroInt.minus_one let get_one_symbol : t -> Symb.Symbol.t = fun x -> match get_one_symbol_opt x with Some s -> s | None -> raise Not_One_Symbol let get_mone_symbol : t -> Symb.Symbol.t = fun x -> match get_mone_symbol_opt x with Some s -> s | None -> raise Not_One_Symbol let is_one_symbol : t -> bool = fun x -> match get_one_symbol_opt x with Some _ -> true | None -> false let is_mone_symbol : t -> bool = fun x -> match get_mone_symbol_opt x with Some _ -> true | None -> false let is_one_symbol_of_common get_symbol_opt ?(weak = false) s x = Option.exists (get_symbol_opt x) ~f:(fun s' -> (if weak then Symb.Symbol.paths_equal else Symb.Symbol.equal) s s' ) let is_one_symbol_of : ?weak:bool -> Symb.Symbol.t -> t -> bool = is_one_symbol_of_common get_one_symbol_opt let is_mone_symbol_of : ?weak:bool -> Symb.Symbol.t -> t -> bool = is_one_symbol_of_common get_mone_symbol_opt let is_signed_one_symbol_of : ?weak:bool -> Sign.t -> Symb.Symbol.t -> t -> bool = fun ?weak sign s x -> match sign with Plus -> is_one_symbol_of ?weak s x | Minus -> is_mone_symbol_of ?weak s x let get_symbols : t -> Symb.SymbolSet.t = fun x -> M.fold (fun symbol _coeff acc -> Symb.SymbolSet.add symbol acc) x Symb.SymbolSet.empty (* we can give integer bounds (obviously 0) only when all symbols are unsigned *) let big_int_lb x = if is_ge_zero x then Some Z.zero else None let big_int_ub x = if is_le_zero x then Some Z.zero else None (** When two following symbols are from the same path, simplify what would lead to a zero sum. E.g. 2 * x.lb - x.ub = x.lb *) let simplify_bound_ends_from_paths : t -> t = fun x -> let f (prev_opt, to_add) symb coeff = match prev_opt with | Some (prev_coeff, prev_symb) when Symb.Symbol.paths_equal prev_symb symb && Bool.(NonZeroInt.is_positive coeff <> NonZeroInt.is_positive prev_coeff) -> let add_coeff = (if NonZeroInt.is_positive coeff then NonZeroInt.max else NonZeroInt.min) prev_coeff (NonZeroInt.( ~- ) coeff) in let to_add = to_add |> M.add symb add_coeff |> M.add prev_symb (NonZeroInt.( ~- ) add_coeff) in (None, to_add) | _ -> (Some (coeff, symb), to_add) in let _, to_add = fold x ~init:(None, zero) ~f in plus x to_add let get_same_one_symbol x1 x2 = match (get_one_symbol_opt x1, get_one_symbol_opt x2) with | Some s1, Some s2 when Symb.Symbol.paths_equal s1 s2 -> Some (Symb.Symbol.path s1) | _ -> None let exists_str ~f x = M.exists (fun k _ -> Symb.Symbol.exists_str ~f k) x end module Bound = struct type min_max = Min | Max [@@deriving compare] module MinMax = struct type t = min_max [@@deriving compare] let neg = function Min -> Max | Max -> Min let eval_big_int x i1 i2 = match x with Min -> Z.min i1 i2 | Max -> Z.max i1 i2 let pp : F.formatter -> t -> unit = fun fmt -> function Min -> F.pp_print_string fmt "min" | Max -> F.pp_print_string fmt "max" end type t = | MInf (** -oo *) | Linear of Z.t * SymLinear.t (** [Linear (c, se)] represents [c+se] where [se] is Σ(c⋅x). *) | MinMax of Z.t * Sign.t * MinMax.t * Z.t * Symb.Symbol.t (** [MinMax] represents a bound of "int [+|-] [min|max](int, symbol)" format. For example, [MinMax (1, Minus, Max, 2, s)] represents [1-max(2,s)]. *) | MinMaxB of MinMax.t * t * t (** [MinMaxB] represents a min/max of two bounds. *) | MultB of Z.t * t * t (** [MultB] represents a multiplication of two bounds. For example, [MultB (1, x, y)] represents [1 + x × y]. *) | PInf (** +oo *) [@@deriving compare] type eval_sym = t Symb.Symbol.eval let equal = [%compare.equal: t] let mask_min_max_constant b = match b with | Linear (_c, x) -> Linear (Z.zero, x) | MinMax (_c, Plus, _m, _d, x) -> Linear (Z.zero, SymLinear.singleton_one x) | MinMax (c, Minus, _m, _d, x) -> Linear (c, SymLinear.singleton_minus_one x) | _ -> b let rec pp_mark : markup:bool -> F.formatter -> t -> unit = let pp_c f c = if not Z.(equal c zero) then if Z.gt c Z.zero then F.fprintf f " + %a" Z.pp_print c else F.fprintf f " - %a" Z.pp_print (Z.neg c) in fun ~markup f -> function | MInf -> F.pp_print_string f "-oo" | PInf -> F.pp_print_string f "+oo" | Linear (c, x) -> if SymLinear.is_zero x then Z.pp_print f c else ( SymLinear.pp ~markup ~is_beginning:true f x ; pp_c f c ) | MinMax (c, sign, m, d, x) -> if Z.(equal c zero) then (Sign.pp ~need_plus:false) f sign else F.fprintf f "%a%a" Z.pp_print c (Sign.pp ~need_plus:true) sign ; F.fprintf f "%a(%a, %a)" MinMax.pp m Z.pp_print d (Symb.Symbol.pp_mark ~markup) x | MinMaxB (m, x, y) -> F.fprintf f "%a(%a, %a)" MinMax.pp m (pp_mark ~markup) x (pp_mark ~markup) y | MultB (c, x, y) -> F.fprintf f "%a%s%a%a" (pp_mark ~markup) x SpecialChars.multiplication_sign (pp_mark ~markup) y pp_c c let pp = pp_mark ~markup:false let of_bound_end = function Symb.BoundEnd.LowerBound -> MInf | Symb.BoundEnd.UpperBound -> PInf let of_big_int : Z.t -> t = fun n -> Linear (n, SymLinear.empty) let of_int : int -> t = fun n -> of_big_int (Z.of_int n) let minf = MInf let mone = of_big_int Z.minus_one let z255 = of_int 255 let zero = of_big_int Z.zero let one = of_big_int Z.one let pinf = PInf let is_some_const : Z.t -> t -> bool = fun c x -> match x with Linear (c', y) -> Z.equal c c' && SymLinear.is_zero y | _ -> false let is_zero : t -> bool = is_some_const Z.zero let is_infty : t -> bool = function MInf | PInf -> true | _ -> false let is_not_infty : t -> bool = function MInf | PInf -> false | _ -> true let is_minf = function MInf -> true | _ -> false let is_pinf = function PInf -> true | _ -> false let of_sym : SymLinear.t -> t = fun s -> Linear (Z.zero, s) let of_foreign_id id = of_sym (SymLinear.singleton_one (Symb.Symbol.of_foreign_id id)) let of_path path_of_partial make_symbol ~unsigned ?non_int partial = let s = make_symbol ~unsigned ?non_int (path_of_partial partial) in of_sym (SymLinear.singleton_one s) let of_normal_path = of_path Symb.SymbolPath.normal let of_offset_path ~is_void = of_path (Symb.SymbolPath.offset ~is_void) ~unsigned:false ~non_int:false let of_length_path ~is_void = of_path (Symb.SymbolPath.length ~is_void) ~unsigned:true ~non_int:false let of_modeled_path = of_path Symb.SymbolPath.modeled ~unsigned:true ~non_int:false let is_path_of ~f = function | Linear (n, se) when Z.(equal n zero) -> Option.value_map (SymLinear.get_one_symbol_opt se) ~default:false ~f:(fun s -> f (Symb.Symbol.path s) ) | _ -> false let is_offset_path_of path = is_path_of ~f:(function | Symb.SymbolPath.Offset {p} -> Symb.SymbolPath.equal_partial p path | _ -> false ) let is_length_path_of path = is_path_of ~f:(function | Symb.SymbolPath.Length {p} -> Symb.SymbolPath.equal_partial p path | _ -> false ) let rec is_symbolic : t -> bool = function | MInf | PInf -> false | Linear (_, se) -> not (SymLinear.is_empty se) | MinMax _ -> true | MinMaxB (_, x, y) | MultB (_, x, y) -> is_symbolic x || is_symbolic y let is_incr_of path = function | Linear (i, se) -> Z.(equal i one) && Option.value_map (SymLinear.get_one_symbol_opt se) ~default:false ~f:(fun sym -> Symb.SymbolPath.equal (Symb.SymbolPath.normal path) (Symb.Symbol.path sym) ) | _ -> false let mk_MinMax (c, sign, m, d, s) = if Symb.Symbol.is_unsigned s && Z.(leq d zero) then match m with | Min -> of_big_int (Sign.eval_big_int sign c d) | Max -> ( match sign with | Plus -> Linear (c, SymLinear.singleton_one s) | Minus -> Linear (c, SymLinear.singleton_minus_one s) ) else MinMax (c, sign, m, d, s) let mk_MultB (n, x, y) = (* NOTE: We have some simplication opportunities here. *) MultB (n, x, y) let big_int_ub_of_minmax = function | MinMax (c, Plus, Min, d, _) -> Some Z.(c + d) | MinMax (c, Minus, Max, d, _) -> Some Z.(c - d) | MinMax (c, Minus, Min, _, s) when Symb.Symbol.is_unsigned s -> Some c | MinMax _ -> None | MinMaxB _ | MultB _ | MInf | PInf | Linear _ -> assert false let big_int_lb_of_minmax = function | MinMax (c, Plus, Max, d, _) -> Some Z.(c + d) | MinMax (c, Minus, Min, d, _) -> Some Z.(c - d) | MinMax (c, Plus, Min, _, s) when Symb.Symbol.is_unsigned s -> Some c | MinMax _ -> None | MinMaxB _ | MultB _ | MInf | PInf | Linear _ -> assert false let big_int_of_minmax = function | Symb.BoundEnd.LowerBound -> big_int_lb_of_minmax | Symb.BoundEnd.UpperBound -> big_int_ub_of_minmax let rec big_int_lb = function | MInf | MultB _ -> None | PInf -> assert false | MinMax _ as b -> big_int_lb_of_minmax b | Linear (c, se) -> SymLinear.big_int_lb se |> Option.map ~f:(Z.( + ) c) | MinMaxB (m, x, y) -> Option.map2 (big_int_lb x) (big_int_lb y) ~f:(MinMax.eval_big_int m) let rec big_int_ub = function | MInf -> assert false | PInf | MultB _ -> None | MinMax _ as b -> big_int_ub_of_minmax b | Linear (c, se) -> SymLinear.big_int_ub se |> Option.map ~f:(Z.( + ) c) | MinMaxB (m, x, y) -> Option.map2 (big_int_ub x) (big_int_ub y) ~f:(MinMax.eval_big_int m) let linear_ub_of_minmax = function | MinMax (c, Plus, Min, _, x) -> Some (Linear (c, SymLinear.singleton_one x)) | MinMax (c, Minus, Max, _, x) -> Some (Linear (c, SymLinear.singleton_minus_one x)) | MinMax _ -> None | MinMaxB _ | MultB _ | MInf | PInf | Linear _ -> assert false let linear_lb_of_minmax = function | MinMax (c, Plus, Max, _, x) -> Some (Linear (c, SymLinear.singleton_one x)) | MinMax (c, Minus, Min, _, x) -> Some (Linear (c, SymLinear.singleton_minus_one x)) | MinMax _ -> None | MinMaxB _ | MultB _ | MInf | PInf | Linear _ -> assert false let le_minmax_by_int x y = match (big_int_ub_of_minmax x, big_int_lb_of_minmax y) with | Some n, Some m -> Z.leq n m | _, _ -> false let le_opt1 le opt_n m = Option.value_map opt_n ~default:false ~f:(fun n -> le n m) let le_opt2 le n opt_m = Option.value_map opt_m ~default:false ~f:(fun m -> le n m) let rec le : t -> t -> bool = fun x y -> match (x, y) with | MInf, _ | _, PInf -> true | _, MInf | PInf, _ -> false | MultB (xc, x1, x2), MultB (yc, y1, y2) -> (* NOTE: We define the order for only straightforward cases. *) Z.leq xc yc && equal x1 y1 && equal x2 y2 | MultB _, _ | _, MultB _ -> false | Linear (c0, x0), Linear (c1, x1) -> Z.leq c0 c1 && SymLinear.le x0 x1 | MinMax _, MinMax _ when le_minmax_by_int x y -> true | MinMax (c1, (Plus as sign), Min, d1, s1), MinMax (c2, Plus, Min, d2, s2) | MinMax (c1, (Minus as sign), Min, d1, s1), MinMax (c2, Minus, Min, d2, s2) | MinMax (c1, (Plus as sign), Max, d1, s1), MinMax (c2, Plus, Max, d2, s2) | MinMax (c1, (Minus as sign), Max, d1, s1), MinMax (c2, Minus, Max, d2, s2) when Symb.Symbol.equal s1 s2 -> Z.leq c1 c2 && let v1 = Sign.eval_big_int sign c1 d1 in let v2 = Sign.eval_big_int sign c2 d2 in Z.leq v1 v2 | MinMax (c1, Plus, Min, _, s1), MinMax (c2, Plus, Max, _, s2) | MinMax (c1, Minus, Max, _, s1), MinMax (c2, Minus, Min, _, s2) when Symb.Symbol.equal s1 s2 -> Z.leq c1 c2 | MinMax _, MinMax _ -> false | MinMax _, Linear (c, se) -> (SymLinear.is_ge_zero se && le_opt1 Z.leq (big_int_ub_of_minmax x) c) || le_opt1 le (linear_ub_of_minmax x) y | Linear (c, se), MinMax _ -> (SymLinear.is_le_zero se && le_opt2 Z.leq c (big_int_lb_of_minmax y)) || le_opt2 le x (linear_lb_of_minmax y) | MinMaxB (Max, x1, x2), y -> le x1 y && le x2 y | MinMaxB (Min, x1, x2), y -> le x1 y || le x2 y | x, MinMaxB (Max, y1, y2) -> le x y1 || le x y2 | x, MinMaxB (Min, y1, y2) -> le x y1 && le x y2 let rec lt : t -> t -> bool = fun x y -> match (x, y) with | MInf, Linear _ | MInf, MinMax _ | MInf, PInf | Linear _, PInf | MinMax _, PInf -> true | MultB (xc, x1, x2), MultB (yc, y1, y2) -> (* NOTE: We define the order for only straightforward cases. *) Z.lt xc yc && equal x1 y1 && equal x2 y2 | MultB _, _ | _, MultB _ -> false | Linear (c, x), _ -> le (Linear (Z.succ c, x)) y | MinMax (c, sign, min_max, d, x), _ -> le (mk_MinMax (Z.succ c, sign, min_max, d, x)) y | MinMaxB (Max, x1, x2), y -> lt x1 y && lt x2 y | MinMaxB (Min, x1, x2), y -> lt x1 y || lt x2 y | x, MinMaxB (Max, y1, y2) -> lt x y1 || lt x y2 | x, MinMaxB (Min, y1, y2) -> lt x y1 && lt x y2 | _, _ -> false let gt : t -> t -> bool = fun x y -> lt y x let eq : t -> t -> bool = fun x y -> le x y && le y x let mk_MinMaxB (m, x, y) = if le x y then match m with Min -> x | Max -> y else if le y x then match m with Min -> y | Max -> x else match (x, y) with | (Linear _ | MinMax _), (Linear _ | MinMax _) -> MinMaxB (m, x, y) | _, _ -> ( match m with Min -> MInf | Max -> PInf ) let of_minmax_bound_min x y = mk_MinMaxB (Min, x, y) let of_minmax_bound_max x y = mk_MinMaxB (Max, x, y) let xcompare = PartialOrder.of_le ~le let is_const : t -> bool = function Linear (_, se) -> SymLinear.is_zero se | _ -> false let rec neg : t -> t = function | MInf -> PInf | PInf -> MInf | Linear (c, x) as b -> if Z.(equal c zero) && SymLinear.is_zero x then b else Linear (Z.neg c, SymLinear.neg x) | MinMax (c, sign, min_max, d, x) -> mk_MinMax (Z.neg c, Sign.neg sign, min_max, d, x) | MinMaxB (m, x, y) -> mk_MinMaxB (MinMax.neg m, neg x, neg y) | MultB (c, x, y) -> mk_MultB (Z.neg c, neg x, y) let rec remove_positive_length_symbol b = match b with | MInf | PInf -> b | Linear (c, x) -> Linear (c, SymLinear.remove_positive_length_symbol x) | MinMax (c, sign, min_max, d, x) -> if Symb.Symbol.is_length x then Linear (Sign.eval_big_int sign c (MinMax.eval_big_int min_max d Z.zero), SymLinear.empty) else b | MinMaxB (m, x, y) -> mk_MinMaxB (m, remove_positive_length_symbol x, remove_positive_length_symbol y) | MultB (c, x, y) -> mk_MultB (c, remove_positive_length_symbol x, remove_positive_length_symbol y) let exact_min : otherwise:(t -> t -> t) -> t -> t -> t = fun ~otherwise b1 b2 -> if le b1 b2 then b1 else if le b2 b1 then b2 else match (b1, b2) with | Linear (c1, x1), Linear (c2, x2) when SymLinear.is_zero x1 && SymLinear.is_one_symbol x2 -> mk_MinMax (c2, Plus, Min, Z.(c1 - c2), SymLinear.get_one_symbol x2) | Linear (c1, x1), Linear (c2, x2) when SymLinear.is_one_symbol x1 && SymLinear.is_zero x2 -> mk_MinMax (c1, Plus, Min, Z.(c2 - c1), SymLinear.get_one_symbol x1) | Linear (c1, x1), Linear (c2, x2) when SymLinear.is_zero x1 && SymLinear.is_mone_symbol x2 -> mk_MinMax (c2, Minus, Max, Z.(c2 - c1), SymLinear.get_mone_symbol x2) | Linear (c1, x1), Linear (c2, x2) when SymLinear.is_mone_symbol x1 && SymLinear.is_zero x2 -> mk_MinMax (c1, Minus, Max, Z.(c1 - c2), SymLinear.get_mone_symbol x1) | MinMax (c1, (Plus as sign), (Min as minmax), _, s), Linear (c2, se) | Linear (c2, se), MinMax (c1, (Plus as sign), (Min as minmax), _, s) | MinMax (c1, (Minus as sign), (Max as minmax), _, s), Linear (c2, se) | Linear (c2, se), MinMax (c1, (Minus as sign), (Max as minmax), _, s) when SymLinear.is_zero se -> let d = Sign.eval_neg_if_minus sign Z.(c2 - c1) in mk_MinMax (c1, sign, minmax, d, s) | MinMax (c1, Plus, Min, d1, s1), Linear (c2, s2) | Linear (c2, s2), MinMax (c1, Plus, Min, d1, s1) when SymLinear.is_one_symbol_of s1 s2 -> let c = Z.min c1 c2 in let d = Z.(c1 + d1) in mk_MinMax (c, Plus, Min, Z.(d - c), s1) | MinMax (c1, Minus, Max, d1, s1), Linear (c2, s2) | Linear (c2, s2), MinMax (c1, Minus, Max, d1, s1) when SymLinear.is_mone_symbol_of s1 s2 -> let c = Z.min c1 c2 in let d = Z.(c1 - d1) in mk_MinMax (c, Minus, Max, Z.(c - d), s1) | MinMax (c1, (Minus as sign), (Max as minmax), d1, s1), MinMax (c2, Minus, Max, d2, s2) | MinMax (c1, (Plus as sign), (Min as minmax), d1, s1), MinMax (c2, Plus, Min, d2, s2) when Symb.Symbol.equal s1 s2 -> let v1 = Sign.eval_big_int sign c1 d1 in let v2 = Sign.eval_big_int sign c2 d2 in let c = Z.min c1 c2 in let v = MinMax.eval_big_int minmax v1 v2 in let d = Sign.eval_neg_if_minus sign Z.(v - c) in mk_MinMax (c, sign, minmax, d, s1) | b1, b2 -> otherwise b1 b2 let rec underapprox_min b1 b2 = exact_min b1 b2 ~otherwise:(fun b1 b2 -> match (b1, b2) with | MinMax (c1, sign, _, d1, _s), Linear (_c2, se) | Linear (_c2, se), MinMax (c1, sign, _, d1, _s) when SymLinear.is_zero se -> Linear (Sign.eval_big_int sign c1 d1, SymLinear.zero) (* There is no best abstraction, we could also use: For Plus, Max: mk_MinMax (c1, Plus, Min, Z.(c2 - c1), s) For Minus, Min: mk_MinMax (c1, Minus, Max, Z.(c1 - c2), s) *) | MinMax (_, Minus, Max, _, _), MinMax (_, Plus, Min, _, _) | MinMax (_, Plus, Min, _, _), MinMax (_, Minus, Max, _, _) -> fallback_underapprox_min b1 b2 | MinMax (c1, (Plus as sign1), Max, d1, _), MinMax (c2, (Minus as sign2), Min, d2, _) | MinMax (c1, (Minus as sign1), Min, d1, _), MinMax (c2, (Plus as sign2), Max, d2, _) -> let v1 = Sign.eval_big_int sign1 c1 d1 in let v2 = Sign.eval_big_int sign2 c2 d2 in Linear (Z.min v1 v2, SymLinear.zero) | MinMax (c1, (Plus as sign), (Max as minmax), d1, s1), MinMax (c2, Plus, Max, d2, s2) | MinMax (c1, (Minus as sign), (Min as minmax), d1, s1), MinMax (c2, Minus, Min, d2, s2) when Symb.Symbol.equal s1 s2 -> let v1 = Sign.eval_big_int sign c1 d1 in let v2 = Sign.eval_big_int sign c2 d2 in let v = Z.min v1 v2 in let c = Z.min c1 c2 in let d = Sign.eval_neg_if_minus sign Z.(v - c) in mk_MinMax (c, sign, minmax, d, s1) | ( MinMax (c1, (Plus as sign1), (Min as minmax1), d1, s1) , MinMax (c2, (Plus as sign2), Max, d2, s2) ) | ( MinMax (c2, (Plus as sign2), Max, d2, s2) , MinMax (c1, (Plus as sign1), (Min as minmax1), d1, s1) ) | ( MinMax (c1, (Minus as sign1), (Max as minmax1), d1, s1) , MinMax (c2, (Minus as sign2), Min, d2, s2) ) | ( MinMax (c2, (Minus as sign2), Min, d2, s2) , MinMax (c1, (Minus as sign1), (Max as minmax1), d1, s1) ) | ( MinMax (c1, (Minus as sign1), (Max as minmax1), d1, s1) , MinMax (c2, (Plus as sign2), Max, d2, s2) ) | ( MinMax (c2, (Plus as sign2), (Max as minmax1), d2, s2) , MinMax (c1, (Minus as sign1), Max, d1, s1) ) | ( MinMax (c1, (Plus as sign1), (Min as minmax1), d1, s1) , MinMax (c2, (Minus as sign2), Min, d2, s2) ) | ( MinMax (c2, (Minus as sign2), (Min as minmax1), d2, s2) , MinMax (c1, (Plus as sign1), Min, d1, s1) ) when Symb.Symbol.equal s1 s2 -> let v1 = Sign.eval_big_int sign1 c1 d1 in let v2 = Sign.eval_big_int sign2 c2 d2 in let v = Z.min v1 v2 in let d = Sign.eval_neg_if_minus sign1 Z.(v - c1) in mk_MinMax (c1, sign1, minmax1, d, s1) | b1, b2 -> fallback_underapprox_min b1 b2 ) and fallback_underapprox_min b1 b2 = match big_int_lb b2 with | Some v2 when not (is_const b2) -> underapprox_min b1 (Linear (v2, SymLinear.zero)) | _ -> ( match big_int_lb b1 with | Some v1 when not (is_const b1) -> underapprox_min (Linear (v1, SymLinear.zero)) b2 | _ -> MInf ) let overapprox_min original_b1 b2 = let overapprox_min b1 b2 = exact_min b1 b2 ~otherwise:(fun b1 b2 -> match (b1, b2) with | ( MinMax (c1, (Minus as sign1), (Max as minmax1), d1, s1) , MinMax (c2, (Plus as sign2), Min, d2, s2) ) | ( MinMax (c1, (Plus as sign1), (Min as minmax1), d1, s1) , MinMax (c2, (Minus as sign2), Max, d2, s2) ) when Symb.Symbol.equal s1 s2 -> let v1 = Sign.eval_big_int sign1 c1 d1 in let v2 = Sign.eval_big_int sign2 c2 d2 in let vmeet = Z.(shift_right (c1 + c2 + one) 1) in let v = Z.(min vmeet (min v1 v2)) in let d = Sign.eval_neg_if_minus sign1 Z.(v - c1) in mk_MinMax (c1, sign1, minmax1, d, s1) | MinMax (c1, (Minus as sign1), Max, d1, s1), MinMax (c2, (Plus as sign2), Min, d2, s2) | MinMax (c1, (Minus as sign1), Min, d1, s1), MinMax (c2, (Plus as sign2), Max, d2, s2) | MinMax (c1, (Plus as sign1), Min, d1, s1), MinMax (c2, (Minus as sign2), Max, d2, s2) | MinMax (c1, (Plus as sign1), Max, d1, s1), MinMax (c2, (Minus as sign2), Min, d2, s2) when Symb.Symbol.equal s1 s2 -> let v1 = Sign.eval_big_int sign1 c1 d1 in let v2 = Sign.eval_big_int sign2 c2 d2 in let vmeet = Z.(shift_right (c1 + c2 + one) 1) in Linear (Z.(max vmeet (max v1 v2)), SymLinear.zero) | (MinMax (_, Plus, Min, _, s1) as b), MinMax (_, Plus, Max, _, s2) | MinMax (_, Plus, Max, _, s2), (MinMax (_, Plus, Min, _, s1) as b) | (MinMax (_, Minus, Min, _, s1) as b), MinMax (_, Minus, Max, _, s2) | MinMax (_, Minus, Max, _, s2), (MinMax (_, Minus, Min, _, s1) as b) when Symb.Symbol.equal s1 s2 -> b | MinMax (c1, Plus, Max, _, s1), MinMax (c2, Plus, Max, _, s2) | MinMax (c1, Minus, Min, _, s1), MinMax (c2, Minus, Min, _, s2) when Symb.Symbol.equal s1 s2 -> if Z.leq c1 c2 then b1 else b2 | ( MinMax (c1, (Minus as sign1), (Max as minmax1), d1, s1) , MinMax (c2, (Plus as sign2), Max, d2, s2) ) | ( MinMax (c2, (Plus as sign2), (Max as minmax1), d2, s2) , MinMax (c1, (Minus as sign1), Max, d1, s1) ) | ( MinMax (c1, (Plus as sign1), (Min as minmax1), d1, s1) , MinMax (c2, (Minus as sign2), Min, d2, s2) ) | ( MinMax (c2, (Minus as sign2), (Min as minmax1), d2, s2) , MinMax (c1, (Plus as sign1), Min, d1, s1) ) when Symb.Symbol.equal s1 s2 -> let v1 = Sign.eval_big_int sign1 c1 d1 in let v2 = Sign.eval_big_int sign2 c2 d2 in let vmin, vmax = if Z.leq v1 v2 then (v1, v2) else (v2, v1) in let vmeet = Z.(shift_right (c1 + c2 + one) 1) in let v = if Z.leq vmin vmeet && Z.leq vmeet vmax then vmeet else vmax in let d = Sign.eval_neg_if_minus sign1 Z.(v - c1) in mk_MinMax (c1, sign1, minmax1, d, s1) | Linear (c1, x1), MinMax (c2, (Minus as sign), Max, d2, _) | Linear (c1, x1), MinMax (c2, (Plus as sign), Min, d2, _) when SymLinear.is_one_symbol x1 -> let d = Sign.eval_big_int sign c2 d2 in mk_MinMax (c1, Plus, Min, Z.(d - c1), SymLinear.get_one_symbol x1) | Linear (c1, x1), MinMax (c2, (Minus as sign), Max, d2, _) | Linear (c1, x1), MinMax (c2, (Plus as sign), Min, d2, _) when SymLinear.is_mone_symbol x1 -> let d = Sign.eval_big_int sign c2 d2 in mk_MinMax (c1, Minus, Max, Z.(c1 - d), SymLinear.get_mone_symbol x1) | _ -> (* When the result is not representable, our best effort is to return the first original argument. Any other deterministic heuristics would work too. *) original_b1 ) in overapprox_min original_b1 b2 let underapprox_max b1 b2 = let res = neg (overapprox_min (neg b1) (neg b2)) in if equal res b1 then b1 else if equal res b2 then b2 else res let overapprox_max b1 b2 = let res = neg (underapprox_min (neg b1) (neg b2)) in if equal res b1 then b1 else if equal res b2 then b2 else res let approx_max = function | Symb.BoundEnd.LowerBound -> underapprox_max | Symb.BoundEnd.UpperBound -> overapprox_max module Thresholds : sig type bound = t type t val make_inc : Z.t list -> t val make_dec : Z.t list -> t val widen : cond:(threshold:bound -> bound -> bool) -> default:bound -> bound -> bound -> t -> bound end = struct type bound = t type t = bound list let default_thresholds = [Z.zero] let make ~compare thresholds = List.dedup_and_sort ~compare (default_thresholds @ thresholds) |> List.map ~f:of_big_int (* It makes a list of thresholds that will be applied with the increasing order. *) let make_inc = make ~compare:Z.compare (* It makes a list of thresholds that will be applied with the decreasing order. *) let make_dec = make ~compare:(fun x y -> -Z.compare x y) let rec widen ~cond ~default x y = function | [] -> default | threshold :: thresholds -> if cond ~threshold x && cond ~threshold y then threshold else widen ~default ~cond x y thresholds end let widen_l_thresholds : thresholds:Z.t list -> t -> t -> t = fun ~thresholds x y -> match (x, y) with | PInf, _ | _, PInf -> L.(die InternalError) "Lower bound cannot be +oo." | MinMax (n1, Plus, Max, _, s1), Linear (n2, s2) when Z.equal n1 n2 && SymLinear.is_one_symbol_of s1 s2 -> y | MinMax (n1, Minus, Min, _, s1), Linear (n2, s2) when Z.equal n1 n2 && SymLinear.is_mone_symbol_of s1 s2 -> y | Linear (n1, s1), MinMax (n2, (Plus as sign1), Min, n3, _) | Linear (n1, s1), MinMax (n2, (Minus as sign1), Max, n3, _) when Z.equal n1 (Sign.eval_big_int sign1 n2 n3) && SymLinear.is_empty s1 -> y | Linear (n1, s1), MinMax (n2, (Plus as sign1), Min, _, s2) | Linear (n1, s1), MinMax (n2, (Minus as sign1), Max, _, s2) when Z.equal n1 n2 && SymLinear.is_signed_one_symbol_of sign1 s2 s1 -> y | _ -> if le x y then x else let cond ~threshold x = le threshold x in Thresholds.widen ~cond ~default:MInf x y (Thresholds.make_dec thresholds) let widen_l : t -> t -> t = fun x y -> widen_l_thresholds ~thresholds:[] x y let widen_u_thresholds : thresholds:Z.t list -> t -> t -> t = fun ~thresholds x y -> match (x, y) with | MInf, _ | _, MInf -> L.(die InternalError) "Upper bound cannot be -oo." | MinMax (n1, Plus, Min, _, s1), Linear (n2, s2) when Z.equal n1 n2 && SymLinear.is_one_symbol_of s1 s2 -> y | MinMax (n1, Minus, Max, _, s1), Linear (n2, s2) when Z.equal n1 n2 && SymLinear.is_mone_symbol_of s1 s2 -> y | Linear (n1, s1), MinMax (n2, (Plus as sign1), Max, n3, _) | Linear (n1, s1), MinMax (n2, (Minus as sign1), Min, n3, _) when Z.equal n1 (Sign.eval_big_int sign1 n2 n3) && SymLinear.is_empty s1 -> y | Linear (n1, s1), MinMax (n2, (Plus as sign1), Max, _, s2) | Linear (n1, s1), MinMax (n2, (Minus as sign1), Min, _, s2) when Z.equal n1 n2 && SymLinear.is_signed_one_symbol_of sign1 s2 s1 -> y | _ -> if le y x then x else let cond ~threshold x = le x threshold in Thresholds.widen ~cond ~default:PInf x y (Thresholds.make_inc thresholds) let widen_u : t -> t -> t = fun x y -> widen_u_thresholds ~thresholds:[] x y let get_const : t -> Z.t option = fun x -> match x with Linear (c, y) when SymLinear.is_zero y -> Some c | _ -> None let rec plus_exact : weak:bool -> otherwise:(t -> t -> t) -> t -> t -> t = fun ~weak ~otherwise x y -> if is_zero x then y else if is_zero y then x else match (x, y) with | Linear (c1, x1), Linear (c2, x2) -> Linear (Z.(c1 + c2), SymLinear.plus x1 x2) | MinMax (c1, sign, min_max, d1, x1), Linear (c2, x2) | Linear (c2, x2), MinMax (c1, sign, min_max, d1, x1) when SymLinear.is_zero x2 -> mk_MinMax (Z.(c1 + c2), sign, min_max, d1, x1) | MinMax (c1, sign, min_max, d, x1), Linear (c2, x2) | Linear (c2, x2), MinMax (c1, sign, min_max, d, x1) when SymLinear.is_signed_one_symbol_of ~weak (Sign.neg sign) x1 x2 -> let c = Sign.eval_big_int sign Z.(c1 + c2) d in mk_MinMax (c, Sign.neg sign, MinMax.neg min_max, d, x1) | MinMaxB (m, x, y), z -> mk_MinMaxB (m, plus_exact ~weak ~otherwise x z, plus_exact ~weak ~otherwise y z) | (MultB (c, x1, x2), Linear (d, se) | Linear (d, se), MultB (c, x1, x2)) when SymLinear.is_zero se -> mk_MultB (Z.add c d, x1, x2) | _ -> otherwise x y let plus_l : weak:bool -> t -> t -> t = plus_exact ~otherwise:(fun x y -> match (x, y) with | MinMax (c1, Plus, Max, d1, _), Linear (c2, x2) | Linear (c2, x2), MinMax (c1, Plus, Max, d1, _) -> Linear (Z.(c1 + d1 + c2), x2) | MinMax (c1, Minus, Min, d1, _), Linear (c2, x2) | Linear (c2, x2), MinMax (c1, Minus, Min, d1, _) -> Linear (Z.(c1 - d1 + c2), x2) | _, _ -> MInf ) let plus_u : weak:bool -> t -> t -> t = plus_exact ~otherwise:(fun x y -> match (x, y) with | MinMax (c1, Plus, Min, d1, _), Linear (c2, x2) | Linear (c2, x2), MinMax (c1, Plus, Min, d1, _) -> Linear (Z.(c1 + d1 + c2), x2) | MinMax (c1, Minus, Max, d1, _), Linear (c2, x2) | Linear (c2, x2), MinMax (c1, Minus, Max, d1, _) -> Linear (Z.(c1 - d1 + c2), x2) | _, _ -> PInf ) let plus = function | Symb.BoundEnd.LowerBound -> plus_l ~weak:false | Symb.BoundEnd.UpperBound -> plus_u ~weak:false let rec mult_const : Symb.BoundEnd.t -> NonZeroInt.t -> t -> t = fun bound_end n x -> if NonZeroInt.is_one n then x else match x with | MInf -> if NonZeroInt.is_positive n then MInf else PInf | PInf -> if NonZeroInt.is_positive n then PInf else MInf | Linear (c, x') -> Linear (Z.(c * (n :> Z.t)), SymLinear.mult_const n x') | MinMax _ -> ( let int_bound = let bound_end' = if NonZeroInt.is_positive n then bound_end else Symb.BoundEnd.neg bound_end in big_int_of_minmax bound_end' x in match int_bound with | Some i -> of_big_int Z.(i * (n :> Z.t)) | None -> of_bound_end bound_end ) | MinMaxB (m, x, y) -> mk_MinMaxB (m, mult_const bound_end n x, mult_const bound_end n y) | MultB _ -> of_bound_end bound_end let mult_const_l = mult_const Symb.BoundEnd.LowerBound let mult_const_u = mult_const Symb.BoundEnd.UpperBound let overapprox_minmax_div_const x (n : NonZeroInt.t) = let c = if NonZeroInt.is_positive n then big_int_ub_of_minmax x else big_int_lb_of_minmax x in Option.map c ~f:(fun c -> Z.(c / (n :> Z.t))) let underapprox_minmax_div_const x (n : NonZeroInt.t) = let c = if NonZeroInt.is_positive n then big_int_lb_of_minmax x else big_int_ub_of_minmax x in Option.map c ~f:(fun c -> Z.(c / (n :> Z.t))) let div_const : Symb.BoundEnd.t -> t -> NonZeroInt.t -> t option = fun bound_end x n -> if NonZeroInt.is_one n then Some x else match x with | MInf -> Some (if NonZeroInt.is_positive n then MInf else PInf) | PInf -> Some (if NonZeroInt.is_positive n then PInf else MInf) | Linear (c, x') when SymLinear.is_zero x' -> Some (Linear (Z.(c / (n :> Z.t)), SymLinear.zero)) | Linear (c, x') when NonZeroInt.is_multiple c n -> ( match SymLinear.exact_div_const_exn x' n with | x'' -> Some (Linear (Z.(c / (n :> Z.t)), x'')) | exception NonZeroInt.DivisionNotExact -> None ) | MinMax _ -> let c = match bound_end with | Symb.BoundEnd.LowerBound -> underapprox_minmax_div_const x n | Symb.BoundEnd.UpperBound -> overapprox_minmax_div_const x n in Option.map c ~f:of_big_int | _ -> None let div_const_l = div_const Symb.BoundEnd.LowerBound let div_const_u = div_const Symb.BoundEnd.UpperBound let rec get_symbols : t -> Symb.SymbolSet.t = function | MInf | PInf -> Symb.SymbolSet.empty | Linear (_, se) -> SymLinear.get_symbols se | MinMax (_, _, _, _, s) -> Symb.SymbolSet.singleton s | MinMaxB (_, x, y) | MultB (_, x, y) -> Symb.SymbolSet.union (get_symbols x) (get_symbols y) let has_void_ptr_symb x = Symb.SymbolSet.exists (fun s -> Symb.SymbolPath.is_void_ptr_path (Symb.Symbol.path s)) (get_symbols x) let are_similar b1 b2 = Symb.SymbolSet.equal (get_symbols b1) (get_symbols b2) (** Substitutes ALL symbols in [x] with respect to [eval_sym]. Under/over-Approximate as good as possible according to [subst_pos]. *) let rec subst : subst_pos:Symb.BoundEnd.t -> t -> eval_sym -> t bottom_lifted = let lift1 : (t -> t) -> t bottom_lifted -> t bottom_lifted = fun f x -> match x with Bottom -> Bottom | NonBottom x -> NonBottom (f x) in let lift2 : (t -> t -> t) -> t bottom_lifted -> t bottom_lifted -> t bottom_lifted = fun f x y -> match (x, y) with | Bottom, _ | _, Bottom -> Bottom | NonBottom x, NonBottom y -> NonBottom (f x y) in fun ~subst_pos x eval_sym -> let get s bound_position = if Language.curr_language_is Java && Symb.Symbol.is_global s then NonBottom (of_sym (SymLinear.singleton_one s)) else match eval_sym s bound_position with | NonBottom x when Symb.Symbol.is_unsigned s -> NonBottom (approx_max subst_pos x zero) | x -> x in let get_mult_const s coeff = let bound_position = if NonZeroInt.is_positive coeff then subst_pos else Symb.BoundEnd.neg subst_pos in if NonZeroInt.is_one coeff then get s bound_position else if NonZeroInt.is_minus_one coeff then get s bound_position |> lift1 neg else match eval_sym s bound_position with | Bottom -> ( (* For unsigned symbols, we can over/under-approximate with zero depending on [bound_position]. *) match (Symb.Symbol.is_unsigned s, bound_position) with | true, Symb.BoundEnd.LowerBound -> NonBottom zero | _ -> Bottom ) | NonBottom x -> let x = mult_const subst_pos coeff x in if Symb.Symbol.is_unsigned s then NonBottom (approx_max subst_pos x zero) else NonBottom x in match x with | MInf | PInf -> NonBottom x | Linear (c, se) -> if SymLinear.is_empty se then NonBottom x else SymLinear.fold se ~init:(NonBottom (of_big_int c)) ~f:(fun acc s coeff -> lift2 (plus subst_pos) acc (get_mult_const s coeff)) | MinMax (c, sign, min_max, d, s) -> ( let bound_position = match sign with Plus -> subst_pos | Minus -> Symb.BoundEnd.neg subst_pos in match get s bound_position with | Bottom -> Option.value_map (big_int_of_minmax subst_pos x) ~default:Bottom ~f:(fun i -> NonBottom (of_big_int i) ) | NonBottom x' -> let res = match (sign, min_max, x') with | Plus, Min, (MInf | MinMaxB _ | MultB _) | Minus, Max, (PInf | MinMaxB _ | MultB _) -> MInf | Plus, Max, (PInf | MinMaxB _ | MultB _) | Minus, Min, (MInf | MinMaxB _ | MultB _) -> PInf | sign, Min, PInf | sign, Max, MInf -> of_big_int (Sign.eval_big_int sign c d) | _, _, Linear (c2, se) -> ( if SymLinear.is_zero se then of_big_int (Sign.eval_big_int sign c (MinMax.eval_big_int min_max d c2)) else if SymLinear.is_one_symbol se then mk_MinMax ( Sign.eval_big_int sign c c2 , sign , min_max , Z.(d - c2) , SymLinear.get_one_symbol se ) else if SymLinear.is_mone_symbol se then mk_MinMax ( Sign.eval_big_int sign c c2 , Sign.neg sign , MinMax.neg min_max , Z.(c2 - d) , SymLinear.get_mone_symbol se ) else match big_int_of_minmax subst_pos x with | Some i -> of_big_int i | None -> of_bound_end subst_pos ) | _, _, MinMax (c2, sign2, min_max2, d2, s2) -> ( match (min_max, sign2, min_max2) with | Min, Plus, Min | Max, Plus, Max -> let c' = Sign.eval_big_int sign c c2 in let d' = MinMax.eval_big_int min_max Z.(d - c2) d2 in mk_MinMax (c', sign, min_max, d', s2) | Min, Minus, Max | Max, Minus, Min -> let c' = Sign.eval_big_int sign c c2 in let d' = MinMax.eval_big_int min_max2 Z.(c2 - d) d2 in mk_MinMax (c', Sign.neg sign, min_max2, d', s2) | _ -> let bound_end = match sign with Plus -> subst_pos | Minus -> Symb.BoundEnd.neg subst_pos in of_big_int (Sign.eval_big_int sign c (MinMax.eval_big_int min_max d (big_int_of_minmax bound_end x' |> Option.value ~default:d))) ) in NonBottom res ) | MinMaxB (m, x, y) -> subst2_merge ~subst_pos x y eval_sym ~f:(fun x y -> mk_MinMaxB (m, x, y)) | MultB (c, x, y) when le zero x && le zero y -> subst2_merge ~subst_pos x y eval_sym ~f:(fun x y -> mk_MultB (c, x, y)) | MultB (c, x, y) when le x zero && le y zero -> let subst_pos = Symb.BoundEnd.neg subst_pos in subst2_merge ~subst_pos x y eval_sym ~f:(fun x y -> mk_MultB (c, x, y)) | MultB _ -> NonBottom (of_bound_end subst_pos) and subst2_merge ~subst_pos x y eval_sym ~f = match (subst ~subst_pos x eval_sym, subst ~subst_pos y eval_sym) with | Bottom, _ | _, Bottom -> Bottom | NonBottom x, NonBottom y -> NonBottom (f x y) let subst_lb x eval_sym = subst ~subst_pos:Symb.BoundEnd.LowerBound x eval_sym let subst_ub x eval_sym = subst ~subst_pos:Symb.BoundEnd.UpperBound x eval_sym (* When a positive bound is expected, min(1,x) can be simplified to 1. *) let simplify_min_one b = match b with | MinMax (c, Plus, Min, d, _x) when Z.(equal c zero) && Z.(equal d one) -> Linear (d, SymLinear.zero) | _ -> b let rec simplify_bound_ends_from_paths x = match x with | MInf | PInf | MinMax _ -> x | Linear (c, se) -> let se' = SymLinear.simplify_bound_ends_from_paths se in if phys_equal se se' then x else Linear (c, se') | MinMaxB (m, a, b) -> let a' = simplify_bound_ends_from_paths a in let b' = simplify_bound_ends_from_paths b in if phys_equal a a' && phys_equal b b' then x else mk_MinMaxB (m, a', b') | MultB (c, a, b) -> let a' = simplify_bound_ends_from_paths a in let b' = simplify_bound_ends_from_paths b in if phys_equal a a' && phys_equal b b' then x else mk_MultB (c, a', b') let simplify_minimum_length x = match x with | MultB _ | Linear _ | MInf | PInf | MinMaxB _ -> x | MinMax (c1, sign, Min, c2, symb) -> let path = Symb.Symbol.path symb in if Symb.SymbolPath.is_length path then let z = Sign.eval_big_int sign c1 (Z.min c2 Z.zero) in Linear (z, SymLinear.empty) else x | MinMax _ -> x let get_same_one_symbol b1 b2 = match (b1, b2) with | Linear (n1, se1), Linear (n2, se2) when Z.(equal n1 zero) && Z.(equal n2 zero) -> SymLinear.get_same_one_symbol se1 se2 | _ -> None let is_same_one_symbol b1 b2 = Option.is_some (get_same_one_symbol b1 b2) let rec exists_str ~f = function | MInf | PInf -> false | Linear (_, s) -> SymLinear.exists_str ~f s | MinMax (_, _, _, _, s) -> Symb.Symbol.exists_str ~f s | MinMaxB (_, x, y) | MultB (_, x, y) -> exists_str ~f x || exists_str ~f y end type ('c, 's, 't) valclass = Constant of 'c | Symbolic of 's | ValTop of 't module BoundTrace = struct type t = | Loop of Location.t | Call of {callee_pname: Procname.t; callee_trace: t; location: Location.t} | ModeledFunction of {pname: string; location: Location.t} | ArcFromNonArc of {pname: string; location: Location.t} | FuncPtr of {path: Symb.SymbolPath.partial; location: Location.t} [@@deriving compare] let rec length = function | Loop _ | ModeledFunction _ | ArcFromNonArc _ | FuncPtr _ -> 1 | Call {callee_trace} -> 1 + length callee_trace let compare t1 t2 = [%compare: int * t] (length t1, t1) (length t2, t2) let join x y = if length x <= length y then x else y let rec pp f = function | Loop loc -> F.fprintf f "Loop (%a)" Location.pp loc | ModeledFunction {pname; location} -> F.fprintf f "ModeledFunction `%s` (%a)" pname Location.pp location | ArcFromNonArc {pname; location} -> F.fprintf f "ArcFromNonArc `%s` (%a)" pname Location.pp location | Call {callee_pname; callee_trace; location} -> F.fprintf f "%a -> Call `%a` (%a)" pp callee_trace Procname.pp callee_pname Location.pp location | FuncPtr {path; location} -> F.fprintf f "FuncPtr `%a` (%a)" Symb.SymbolPath.pp_partial path Location.pp location let call ~callee_pname ~location callee_trace = Call {callee_pname; callee_trace; location} let rec is_func_ptr = function | Call {callee_trace} -> is_func_ptr callee_trace | FuncPtr _ -> true | Loop _ | ModeledFunction _ | ArcFromNonArc _ -> false let rec make_err_trace_of_non_func_ptr ~depth trace = match trace with | Loop loop_head_loc -> [Errlog.make_trace_element depth loop_head_loc "Loop" []] | Call {callee_pname; location; callee_trace} -> let desc = F.asprintf "Call to %a" Procname.pp callee_pname in Errlog.make_trace_element depth location desc [] :: make_err_trace_of_non_func_ptr ~depth:(depth + 1) callee_trace | ModeledFunction {pname; location} -> let desc = F.asprintf "Modeled call to %s" pname in [Errlog.make_trace_element depth location desc []] | ArcFromNonArc {pname; location} -> let desc = F.asprintf "ARC function call to %s from non-ARC caller" pname in [Errlog.make_trace_element depth location desc []] | FuncPtr _ -> assert false let make_err_trace ~depth trace = (* Function pointer trace is suppressed. *) if is_func_ptr trace then [] else make_err_trace_of_non_func_ptr ~depth trace let of_loop location = Loop location let of_modeled_function pname location = ModeledFunction {pname; location} let of_arc_from_non_arc pname location = ArcFromNonArc {pname; location} let of_function_ptr path location = FuncPtr {path; location} let rec subst ~get_autoreleasepool_trace x = match x with | Call {callee_pname; callee_trace; location} -> subst ~get_autoreleasepool_trace callee_trace |> Option.map ~f:(fun callee_trace' -> if phys_equal callee_trace callee_trace' then x else Call {callee_pname; callee_trace= callee_trace'; location} ) | FuncPtr {path} -> get_autoreleasepool_trace path | Loop _ | ModeledFunction _ | ArcFromNonArc _ -> Some x end (** A NonNegativeBound is a Bound that is either non-negative or symbolic but will be evaluated to a non-negative value once instantiated *) module NonNegativeBound = struct type t = Bound.t * BoundTrace.t [@@deriving compare] let leq ~lhs:(bound_lhs, _) ~rhs:(bound_rhs, _) = Bound.le bound_lhs bound_rhs let join (bound_x, trace_x) (bound_y, trace_y) = (Bound.overapprox_max bound_x bound_y, BoundTrace.join trace_x trace_y) let widen ~prev:(bound_prev, trace_prev) ~next:(bound_next, trace_next) ~num_iters:_ = (Bound.widen_u bound_prev bound_next, BoundTrace.join trace_prev trace_next) let make_err_trace (b, t) = let b = F.asprintf "{%a}" Bound.pp b in (b, BoundTrace.make_err_trace ~depth:0 t) let pp ~hum fmt (bound, t) = Bound.pp fmt bound ; if not hum then F.fprintf fmt ": %a" BoundTrace.pp t let mask_min_max_constant (b, bt) = (Bound.mask_min_max_constant b, bt) let zero loop_head_loc = (Bound.zero, BoundTrace.Loop loop_head_loc) let check_le_zero b = if Bound.le b Bound.zero then Bound.zero else b let of_bound ~trace b = (check_le_zero b, trace) let of_loop_bound loop_head_loc = of_bound ~trace:(BoundTrace.Loop loop_head_loc) let of_modeled_function pname location b = if Bound.lt b Bound.zero then (* we shouldn't have negative modeled bounds *) assert false else (b, BoundTrace.ModeledFunction {pname; location}) let of_big_int ~trace c = (Bound.of_big_int c, trace) let int_lb (b, _) = Bound.big_int_lb b |> Option.bind ~f:NonNegativeInt.of_big_int |> Option.value ~default:NonNegativeInt.zero let int_ub (b, _) = Bound.big_int_ub b |> Option.map ~f:NonNegativeInt.of_big_int_exn let classify (b, trace) = match b with | Bound.PInf -> ValTop trace | Bound.MInf -> assert false | b -> ( match Bound.get_const b with | None -> Symbolic (b, trace) | Some c -> Constant (NonNegativeInt.of_big_int_exn c) ) let subst callee_pname location (b, callee_trace) map = match Bound.subst_ub b map with | Bottom -> Constant NonNegativeInt.zero | NonBottom b -> of_bound b ~trace:(BoundTrace.call ~callee_pname ~location callee_trace) |> classify let split_mult (b, trace) = match b with Bound.MultB (_, b1, b2) -> Some ((b1, trace), (b2, trace)) | _ -> None end