You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1452 lines
51 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

(*
* 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