From 39af0ccd29dbef33a9679fd44217ccab4d843dcb Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Fri, 6 May 2022 17:09:47 +0200 Subject: [PATCH 01/54] Inject dummy float domain into analysis framework --- src/analyses/base.ml | 3 ++ src/cdomains/baseDomain.ml | 1 + src/cdomains/floatDomain.ml | 45 ++++++++++++++++++++++++++++ src/cdomains/floatDomain.mli | 42 ++++++++++++++++++++++++++ src/cdomains/preValueDomain.ml | 3 +- src/cdomains/valueDomain.ml | 24 ++++++++++++++- tests/regression/56-floats/01-base.c | 17 +++++++++++ 7 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 src/cdomains/floatDomain.ml create mode 100644 src/cdomains/floatDomain.mli create mode 100644 tests/regression/56-floats/01-base.c diff --git a/src/analyses/base.ml b/src/analyses/base.ml index bd7ba92696..8cc2674e9c 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -232,6 +232,7 @@ struct | `Int v1, `Int v2 -> let result_ik = Cilfacade.get_ikind t in `Int (ID.cast_to result_ik (binop_ID result_ik op v1 v2)) + | `Float v1, `Float v2 -> failwith "todo: float binop" (* For address +/- value, we try to do some elementary ptr arithmetic *) | `Address p, `Int n | `Int n, `Address p when op=Eq || op=Ne -> @@ -430,6 +431,7 @@ struct | `List e -> reachable_from_value ask gs st (`Address (ValueDomain.Lists.entry_rand e)) t description | `Struct s -> ValueDomain.Structs.fold (fun k v acc -> AD.join (reachable_from_value ask gs st v t description) acc) s empty | `Int _ -> empty + | `Float _ -> failwith "todo" | `Thread _ -> empty (* thread IDs are abstract and nothing known can be reached from them *) (* Get the list of addresses accessable immediately from a given address, thus @@ -567,6 +569,7 @@ struct in ValueDomain.Structs.fold f s (empty, TS.bot (), false) | `Int _ -> (empty, TS.bot (), false) + | `Float _ -> failwith "todo" | `Thread _ -> (empty, TS.bot (), false) (* TODO: is this right? *) in reachable_from_value (get (Analyses.ask_of_ctx ctx) ctx.global ctx.local adr None) diff --git a/src/cdomains/baseDomain.ml b/src/cdomains/baseDomain.ml index 091286f0cc..cc94e361dc 100644 --- a/src/cdomains/baseDomain.ml +++ b/src/cdomains/baseDomain.ml @@ -196,6 +196,7 @@ module DomWithTrivialExpEval (PrivD: Lattice.S) = DomFunctor (PrivD) (struct begin match CPA.find v r.cpa with | `Int i -> ValueDomain.ID.to_int i + | `Float f -> failwith "todo, change actually required here?" | _ -> None end | _ -> None diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml new file mode 100644 index 0000000000..4d425c7fa1 --- /dev/null +++ b/src/cdomains/floatDomain.ml @@ -0,0 +1,45 @@ +module FloatDomTuple = +struct +type t = (float * float) option + +let equal _ _ = false +let hash _ = 0 +let compare _ _ = 0 +let show _ = failwith "todo" +let pretty () (x: t) = failwith "todo" +let printXml _ (x: t) = failwith "todo" +let name () = failwith "todo" +let to_yojson (x: t) = failwith "todo" +let invariant _ (x: t) = failwith "todo" +let tag (x: t) = failwith "todo" +let arbitrary () = failwith "todo" +let relift (x: t) = failwith "todo" + +let leq _ _ = false +let join _ _ = None +let meet _ _ = None +let widen _ _ = None (** [widen x y] assumes [leq x y]. Solvers guarantee this by calling [widen old (join old new)]. *) + +let narrow _ _ = None + +(** If [leq x y = false], then [pretty_diff () (x, y)] should explain why. *) +let pretty_diff () _ = failwith "todo" + +let bot () = None +let is_bot _ = false +let top () = None +let is_top _ = false + +let neg _ = None +let add _ _ = None +let sub _ _ = None +let mul _ _ = None +let div _ _ = None + +let lt _ _ = None +let gt _ _ = None +let le _ _ = None +let ge _ _ = None +let eq _ _ = None +let ne _ _ = None +end diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli new file mode 100644 index 0000000000..0c986aad7e --- /dev/null +++ b/src/cdomains/floatDomain.mli @@ -0,0 +1,42 @@ +(** Abstract Domains for floats. These are domains that support the C + * operations on double/float values. *) + + module FloatDomTuple : sig + include Lattice.S + + val neg: t -> t + (** Negating an integer value: [-x] *) + + val add: t -> t -> t + (** Addition: [x + y] *) + + val sub: t -> t -> t + (** Subtraction: [x - y] *) + + val mul: t -> t -> t + (** Multiplication: [x * y] *) + + val div: t -> t -> t + (** Division: [x / y] *) + + (** {b Comparison operators} *) + + val lt: t -> t -> t + (** Less than: [x < y] *) + + val gt: t -> t -> t + (** Greater than: [x > y] *) + + val le: t -> t -> t + (** Less than or equal: [x <= y] *) + + val ge: t -> t -> t + (** Greater than or equal: [x >= y] *) + + val eq: t -> t -> t + (** Equal to: [x == y] *) + + val ne: t -> t -> t + (** Not equal to: [x != y] *) + end + diff --git a/src/cdomains/preValueDomain.ml b/src/cdomains/preValueDomain.ml index 02535f30bc..594241973d 100644 --- a/src/cdomains/preValueDomain.ml +++ b/src/cdomains/preValueDomain.ml @@ -1,4 +1,5 @@ module ID = IntDomain.IntDomTuple +module FD = FloatDomain.FloatDomTuple module IndexDomain = IntDomain.IntDomWithDefaultIkind (ID) (IntDomain.PtrDiffIkind) module AD = AddressDomain.AddressSet (IndexDomain) -module Addr = Lval.NormalLat (IndexDomain) \ No newline at end of file +module Addr = Lval.NormalLat (IndexDomain) diff --git a/src/cdomains/valueDomain.ml b/src/cdomains/valueDomain.ml index b66cec2bb1..145d1882ca 100644 --- a/src/cdomains/valueDomain.ml +++ b/src/cdomains/valueDomain.ml @@ -78,6 +78,7 @@ module Threads = ConcDomain.ThreadSet module rec Compound: S with type t = [ | `Top | `Int of ID.t + | `Float of FD.t | `Address of AD.t | `Struct of Structs.t | `Union of Unions.t @@ -91,6 +92,7 @@ struct type t = [ | `Top | `Int of ID.t + | `Float of FD.t | `Address of AD.t | `Struct of Structs.t | `Union of Unions.t @@ -115,6 +117,7 @@ struct let rec bot_value (t: typ): t = match t with | TInt _ -> `Bot (*`Int (ID.bot ()) -- should be lower than any int or address*) + | TFloat _ -> failwith "todo" | TPtr _ -> `Address (AD.bot ()) | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> bot_value fd.ftype) ci) | TComp ({cstruct=false; _},_) -> `Union (Unions.bot ()) @@ -130,6 +133,7 @@ struct let is_bot_value x = match x with | `Int x -> ID.is_bot x + | `Float x -> FD.is_bot x | `Address x -> AD.is_bot x | `Struct x -> Structs.is_bot x | `Union x -> Unions.is_bot x @@ -144,6 +148,7 @@ struct match t with | t when is_mutex_type t -> `Top | TInt (ik,_) -> `Int (ID.top_of ik) + | TFloat (FDouble, _) -> `Float (FD.top ()) (* TODO(Practical2022): extend to other floating point types *) | TPtr _ -> `Address AD.top_ptr | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> init_value fd.ftype) ci) | TComp ({cstruct=false; _},_) -> `Union (Unions.top ()) @@ -159,6 +164,7 @@ struct let rec top_value (t: typ): t = match t with | TInt (ik,_) -> `Int (ID.(cast_to ik (top_of ik))) + | TFloat (FDouble, _) -> `Float (FD.top ()) (* TODO(Practical2022): extend to other floating point types *) | TPtr _ -> `Address AD.top_ptr | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> top_value fd.ftype) ci) | TComp ({cstruct=false; _},_) -> `Union (Unions.top ()) @@ -173,6 +179,7 @@ struct let is_top_value x (t: typ) = match x with | `Int x -> ID.is_top_of (Cilfacade.get_ikind (t)) x + | `Float x -> FD.is_top x | `Address x -> AD.is_top x | `Struct x -> Structs.is_top x | `Union x -> Unions.is_top x @@ -186,6 +193,7 @@ struct let rec zero_init_value (t:typ): t = match t with | TInt (ikind, _) -> `Int (ID.of_int ikind BI.zero) + | TFloat (FDouble, _) -> failwith "todo" | TPtr _ -> `Address AD.null_ptr | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> zero_init_value fd.ftype) ci) | TComp ({cstruct=false; _} as ci,_) -> @@ -208,7 +216,7 @@ struct | _ -> `Top let tag_name : t -> string = function - | `Top -> "Top" | `Int _ -> "Int" | `Address _ -> "Address" | `Struct _ -> "Struct" | `Union _ -> "Union" | `Array _ -> "Array" | `Blob _ -> "Blob" | `List _ -> "List" | `Thread _ -> "Thread" | `Bot -> "Bot" + | `Top -> "Top" | `Int _ -> "Int" | `Float _ -> "Float" | `Address _ -> "Address" | `Struct _ -> "Struct" | `Union _ -> "Union" | `Array _ -> "Array" | `Blob _ -> "Blob" | `List _ -> "List" | `Thread _ -> "Thread" | `Bot -> "Bot" include Printable.Std let name () = "compound" @@ -226,6 +234,7 @@ struct let pretty () state = match state with | `Int n -> ID.pretty () n + | `Float n -> FD.pretty () n | `Address n -> AD.pretty () n | `Struct n -> Structs.pretty () n | `Union n -> Unions.pretty () n @@ -239,6 +248,7 @@ struct let show state = match state with | `Int n -> ID.show n + | `Float n -> FD.show n | `Address n -> AD.show n | `Struct n -> Structs.show n | `Union n -> Unions.show n @@ -252,6 +262,7 @@ struct let pretty_diff () (x,y) = match (x,y) with | (`Int x, `Int y) -> ID.pretty_diff () (x,y) + | (`Float x, `Float y) -> FD.pretty_diff () (x,y) | (`Address x, `Address y) -> AD.pretty_diff () (x,y) | (`Struct x, `Struct y) -> Structs.pretty_diff () (x,y) | (`Union x, `Union y) -> Unions.pretty_diff () (x,y) @@ -444,6 +455,7 @@ struct | (`Bot, _) -> true | (_, `Bot) -> false | (`Int x, `Int y) -> ID.leq x y + | (`Float x, `Float y) -> FD.leq x y | (`Int x, `Address y) when ID.to_int x = Some BI.zero && not (AD.is_not_null y) -> true | (`Int _, `Address y) when AD.may_be_unknown y -> true | (`Address _, `Int y) when ID.is_top_of (Cilfacade.ptrdiff_ikind ()) y -> true @@ -467,6 +479,7 @@ struct | (`Bot, x) -> x | (x, `Bot) -> x | (`Int x, `Int y) -> (try `Int (ID.join x y) with IntDomain.IncompatibleIKinds m -> Messages.warn "%s" m; `Top) + | (`Float x, `Float y) -> `Float (FD.join x y) (* TODO(Practical2022): type check?! *) | (`Int x, `Address y) | (`Address y, `Int x) -> `Address (match ID.to_int x with | Some x when BI.equal x BI.zero -> AD.join AD.null_ptr y @@ -501,6 +514,7 @@ struct | (`Bot, x) -> x | (x, `Bot) -> x | (`Int x, `Int y) -> (try `Int (ID.join x y) with IntDomain.IncompatibleIKinds m -> Messages.warn "%s" m; `Top) + | (`Float x, `Float y) -> `Float (FD.join x y) (* TODO(Practical2022): type check?! *) | (`Int x, `Address y) | (`Address y, `Int x) -> `Address (match ID.to_int x with | Some x when BI.equal BI.zero x -> AD.join AD.null_ptr y @@ -536,6 +550,7 @@ struct | (`Bot, x) -> x | (x, `Bot) -> x | (`Int x, `Int y) -> (try `Int (ID.widen x y) with IntDomain.IncompatibleIKinds m -> Messages.warn "%s" m; `Top) + | (`Float x, `Float y) -> `Float (FD.widen x y) (* TODO(Practical2022): type check?! *) | (`Int x, `Address y) | (`Address y, `Int x) -> `Address (match ID.to_int x with | Some x when BI.equal BI.zero x -> AD.widen AD.null_ptr y @@ -569,6 +584,7 @@ struct | (`Bot, _) -> true | (_, `Bot) -> false | (`Int x, `Int y) -> ID.leq x y + | (`Float x, `Float y) -> FD.leq x y | (`Int x, `Address y) when ID.to_int x = Some BI.zero && not (AD.is_not_null y) -> true | (`Int _, `Address y) when AD.may_be_unknown y -> true | (`Address _, `Int y) when ID.is_top_of (Cilfacade.ptrdiff_ikind ()) y -> true @@ -592,6 +608,7 @@ struct | (`Top, x) -> x | (x, `Top) -> x | (`Int x, `Int y) -> `Int (ID.meet x y) + | (`Float x, `Float y) -> `Float (FD.meet x y) | (`Int _, `Address _) -> meet x (cast (TInt(ptr_ikind (),[])) y) | (`Address x, `Int y) -> `Address (AD.meet x (AD.of_int (module ID:IntDomain.Z with type t = ID.t) y)) | (`Address x, `Address y) -> `Address (AD.meet x y) @@ -618,6 +635,7 @@ struct | (`Bot, x) -> x | (x, `Bot) -> x | (`Int x, `Int y) -> (try `Int (ID.widen x y) with IntDomain.IncompatibleIKinds m -> Messages.warn "%s" m; `Top) + | (`Float x, `Float y) -> `Float (FD.widen x y) | (`Int x, `Address y) | (`Address y, `Int x) -> `Address (match ID.to_int x with | Some x when BI.equal x BI.zero -> AD.widen AD.null_ptr y @@ -645,6 +663,7 @@ struct let rec narrow x y = match (x,y) with | (`Int x, `Int y) -> `Int (ID.narrow x y) + | (`Float x, `Float y) -> `Float (FD.narrow x y) | (`Int _, `Address _) -> narrow x (cast IntDomain.Size.top_typ y) | (`Address x, `Int y) -> `Address (AD.narrow x (AD.of_int (module ID:IntDomain.Z with type t = ID.t) y)) | (`Address x, `Address y) -> `Address (AD.narrow x y) @@ -1077,6 +1096,7 @@ struct let printXml f state = match state with | `Int n -> ID.printXml f n + | `Float n -> FD.printXml f n | `Address n -> AD.printXml f n | `Struct n -> Structs.printXml f n | `Union n -> Unions.printXml f n @@ -1089,6 +1109,7 @@ struct let to_yojson = function | `Int n -> ID.to_yojson n + | `Float n -> FD.to_yojson n | `Address n -> AD.to_yojson n | `Struct n -> Structs.to_yojson n | `Union n -> Unions.to_yojson n @@ -1112,6 +1133,7 @@ struct let rec project p (v: t): t = match v with | `Int n -> `Int (ID.project p n) + | `Float n -> failwith "todo" | `Address n -> `Address (project_addr p n) | `Struct n -> `Struct (Structs.map (fun (x: t) -> project p x) n) | `Union (f, v) -> `Union (f, project p v) diff --git a/tests/regression/56-floats/01-base.c b/tests/regression/56-floats/01-base.c new file mode 100644 index 0000000000..f73cfbd9fc --- /dev/null +++ b/tests/regression/56-floats/01-base.c @@ -0,0 +1,17 @@ +#include +#include +#include +#include + +int main() +{ + double middle; + double a = DBL_MAX; + double b = DBL_MAX; + + middle = middle * 1.; // of course all accesses before initialization should result in a warning + + middle = (a + b) / 2.; // naive way of computing the middle + + return middle - 100.; // further uses of the overflown middle value should result in a warning +} From 87a9398d5c1e618338dd51de53704f84a7f04cdd Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Sat, 7 May 2022 16:04:30 +0200 Subject: [PATCH 02/54] Implement basic functionality for covering const-propagated asserts --- src/analyses/base.ml | 34 +++++- src/cdomains/floatDomain.ml | 170 ++++++++++++++++++++------- src/cdomains/floatDomain.mli | 16 +-- src/cdomains/valueDomain.ml | 4 +- tests/regression/56-floats/01-base.c | 15 ++- 5 files changed, 181 insertions(+), 58 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 8cc2674e9c..5f70db9003 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -10,6 +10,7 @@ module Q = Queries module GU = Goblintutil module ID = ValueDomain.ID +module FD = ValueDomain.FD module IdxDom = ValueDomain.IndexDomain module AD = ValueDomain.AD module Addr = ValueDomain.Addr @@ -127,10 +128,16 @@ struct | Neg -> ID.neg | BNot -> ID.bitnot | LNot -> ID.lognot + + let unop_FD = function + | Neg -> FD.neg + (* other unary operators are not implemented on float values *) + | _ -> (fun _ -> FD.top ()) (* Evaluating Cil's unary operators. *) let evalunop op typ = function | `Int v1 -> `Int (ID.cast_to (Cilfacade.get_ikind typ) (unop_ID op v1)) + | `Float v -> `Float (unop_FD op v) (* TODO(Practical2022): Do we require a type check? *) | `Address a when op = LNot -> if AD.is_null a then `Int (ID.of_bool (Cilfacade.get_ikind typ) true) @@ -162,6 +169,26 @@ struct | LOr -> ID.logor | b -> (fun x y -> (ID.top_of result_ik)) + let binop_FD = function + | PlusA -> FD.add + | MinusA -> FD.sub + | Mult -> FD.mul + | Div -> FD.div + | _ -> (fun _ _ -> FD.top ()) + + let int_returning_binop_FD = function + | Lt -> FD.lt + | Gt -> FD.gt + | Le -> FD.le + | Ge -> FD.ge + | Eq -> FD.eq + | Ne -> FD.ne + | _ -> (fun _ _ -> ID.top ()) + + let is_int_returning_binop_FD = function + | Lt | Gt | Le | Ge | Eq | Ne -> true + | _ -> false + (* Evaluate binop for two abstract values: *) let evalbinop (a: Q.ask) (st: store) (op: binop) (t1:typ) (a1:value) (t2:typ) (a2:value) (t:typ) :value = if M.tracing then M.tracel "eval" "evalbinop %a %a %a\n" d_binop op VD.pretty a1 VD.pretty a2; @@ -232,7 +259,10 @@ struct | `Int v1, `Int v2 -> let result_ik = Cilfacade.get_ikind t in `Int (ID.cast_to result_ik (binop_ID result_ik op v1 v2)) - | `Float v1, `Float v2 -> failwith "todo: float binop" + | `Float v1, `Float v2 when is_int_returning_binop_FD op -> + let result_ik = Cilfacade.get_ikind t in + `Int (ID.cast_to result_ik (int_returning_binop_FD op v1 v2)) + | `Float v1, `Float v2 -> `Float (binop_FD op v1 v2) (* For address +/- value, we try to do some elementary ptr arithmetic *) | `Address p, `Int n | `Int n, `Address p when op=Eq || op=Ne -> @@ -697,6 +727,7 @@ struct | Const (CInt (num,ikind,str)) -> (match str with Some x -> M.tracel "casto" "CInt (%s, %a, %s)\n" (Cilint.string_of_cilint num) d_ikind ikind x | None -> ()); `Int (ID.cast_to ikind (IntDomain.of_const (num,ikind,str))) + | Const (CReal (num, _, _)) -> `Float (FD.of_const num) (* TODO(Practical(2022): use string representation instead *) (* String literals *) | Const (CStr (x,_)) -> `Address (AD.from_string x) (* normal 8-bit strings, type: char* *) | Const (CWStr (xs,_) as c) -> (* wide character strings, type: wchar_t* *) @@ -1436,6 +1467,7 @@ struct helper Ne x (null_val (Cilfacade.typeOf exp)) tv | UnOp (LNot,uexp,typ) -> derived_invariant uexp (not tv) | _ -> + (*TODO(Practical2022): Make goblint also understand floats *) if M.tracing then M.tracec "invariant" "Failed! (expression %a not understood)\n\n" d_plainexp exp; None in diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 4d425c7fa1..d75ee96bb2 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -1,45 +1,127 @@ -module FloatDomTuple = -struct -type t = (float * float) option - -let equal _ _ = false -let hash _ = 0 -let compare _ _ = 0 -let show _ = failwith "todo" -let pretty () (x: t) = failwith "todo" -let printXml _ (x: t) = failwith "todo" -let name () = failwith "todo" -let to_yojson (x: t) = failwith "todo" -let invariant _ (x: t) = failwith "todo" -let tag (x: t) = failwith "todo" -let arbitrary () = failwith "todo" -let relift (x: t) = failwith "todo" - -let leq _ _ = false -let join _ _ = None -let meet _ _ = None -let widen _ _ = None (** [widen x y] assumes [leq x y]. Solvers guarantee this by calling [widen old (join old new)]. *) - -let narrow _ _ = None - -(** If [leq x y = false], then [pretty_diff () (x, y)] should explain why. *) -let pretty_diff () _ = failwith "todo" - -let bot () = None -let is_bot _ = false -let top () = None -let is_top _ = false - -let neg _ = None -let add _ _ = None -let sub _ _ = None -let mul _ _ = None -let div _ _ = None - -let lt _ _ = None -let gt _ _ = None -let le _ _ = None -let ge _ _ = None -let eq _ _ = None -let ne _ _ = None +open Pretty + +let eval_binop operation op1 op2 = + match (op1, op2) with Some v1, Some v2 -> operation v1 v2 | _ -> None + +let eval_int_binop operation op1 op2 = + let a, b = + match (op1, op2) with Some v1, Some v2 -> operation v1 v2 | _ -> (0, 1) + in + IntDomain.IntDomTuple.of_interval IBool + (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) + +let add (low1, high1) (low2, high2) = Some (low1 +. low2, high1 +. high2) + +let sub (low1, high1) (low2, high2) = Some (low1 -. low2, high1 -. high2) + +let mul (low1, high1) (low2, high2) = + let mul1 = low1 *. low2 in + let mul2 = low1 *. high2 in + let mul3 = high1 *. low2 in + let mul4 = high1 *. high2 in + let low = min (min (min mul1 mul2) mul3) mul4 in + let high = max (max (max mul1 mul2) mul3) mul4 in + Some (low, high) + +let div (low1, high1) (low2, high2) = + if low2 < 0. && high2 > 0. then None + else + let mul1 = low1 /. low2 in + let mul2 = low1 /. high2 in + let mul3 = high1 /. low2 in + let mul4 = high1 /. high2 in + let low = min (min (min mul1 mul2) mul3) mul4 in + let high = max (max (max mul1 mul2) mul3) mul4 in + Some (low, high) + +let eq (low1, high1) (low2, high2) = + if high1 < low2 || high2 < low1 then (0, 0) + else if high1 = low1 && high2 = low2 && low1 = low2 then (1, 1) + else (0, 1) + +let ne (low1, high1) (low2, high2) = + if high1 < low2 || high2 < low1 then (1, 1) + else if high1 == low1 && high2 == low2 && low1 == low2 then (0, 0) + else (0, 1) + +let lt (low1, high1) (low2, high2) = + if high1 < low2 then (1, 1) else if low1 > high2 then (0, 0) else (0, 1) + +let gt (low1, high1) (low2, high2) = + if low1 > high2 then (1, 1) else if high1 < low2 then (0, 0) else (0, 1) + +module FloatDomTuple = struct + type t = (float * float) option [@@deriving eq, to_yojson] + + let of_const f = Some (f, f) + + let hash = Hashtbl.hash + + let compare _ _ = failwith "todo" + + let show = function + | None -> + "Float [arbitrary]" + | Some (low, high) -> + "Float [" ^ string_of_float low ^ "," ^ string_of_float high ^ "]" + + let pretty () x = text (show x) + + let printXml f (x : t) = + BatPrintf.fprintf f "\n\n%s\n\n\n" (show x) + + let name () = "float interval domain" + + let invariant _ (x : t) = failwith "todo" + + let tag (x : t) = failwith "todo" + + let arbitrary () = failwith "todo" + + let relift (x : t) = failwith "todo" + + let leq _ _ = failwith "todo" + + let join _ _ = failwith "todo" + + let meet _ _ = failwith "todo" + + (** [widen x y] assumes [leq x y]. Solvers guarantee this by calling [widen old (join old new)]. *) + let widen _ _ = failwith "todo" + + let narrow _ _ = failwith "todo" + + (** If [leq x y = false], then [pretty_diff () (x, y)] should explain why. *) + let pretty_diff () (x, y) = + Pretty.dprintf "%a instead of %a" pretty x pretty y + + let bot () = failwith "no bot exists" + + let is_bot _ = failwith "no bot exists" + + let top () = None + + let is_top = Option.is_none + + let neg = Option.map (fun (low, high) -> (-.high, -.low)) + + let add = eval_binop add + + let sub = eval_binop sub + + let mul = eval_binop mul + + let div = eval_binop div + + let lt = eval_int_binop lt + + let gt = eval_int_binop gt + + let le _ _ = failwith "todo - le" + + let ge _ _ = failwith "todo - ge" + + let eq = eval_int_binop eq + + let ne = eval_int_binop ne end diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index 0c986aad7e..4f47d265f4 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -4,8 +4,10 @@ module FloatDomTuple : sig include Lattice.S + val of_const: float -> t + val neg: t -> t - (** Negating an integer value: [-x] *) + (** Negating an flaot value: [-x] *) val add: t -> t -> t (** Addition: [x + y] *) @@ -21,22 +23,22 @@ (** {b Comparison operators} *) - val lt: t -> t -> t + val lt: t -> t -> IntDomain.IntDomTuple.t (** Less than: [x < y] *) - val gt: t -> t -> t + val gt: t -> t -> IntDomain.IntDomTuple.t (** Greater than: [x > y] *) - val le: t -> t -> t + val le: t -> t -> IntDomain.IntDomTuple.t (** Less than or equal: [x <= y] *) - val ge: t -> t -> t + val ge: t -> t -> IntDomain.IntDomTuple.t (** Greater than or equal: [x >= y] *) - val eq: t -> t -> t + val eq: t -> t -> IntDomain.IntDomTuple.t (** Equal to: [x == y] *) - val ne: t -> t -> t + val ne: t -> t -> IntDomain.IntDomTuple.t (** Not equal to: [x != y] *) end diff --git a/src/cdomains/valueDomain.ml b/src/cdomains/valueDomain.ml index 145d1882ca..9a3bef4cf1 100644 --- a/src/cdomains/valueDomain.ml +++ b/src/cdomains/valueDomain.ml @@ -117,7 +117,7 @@ struct let rec bot_value (t: typ): t = match t with | TInt _ -> `Bot (*`Int (ID.bot ()) -- should be lower than any int or address*) - | TFloat _ -> failwith "todo" + | TFloat _ -> `Bot | TPtr _ -> `Address (AD.bot ()) | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> bot_value fd.ftype) ci) | TComp ({cstruct=false; _},_) -> `Union (Unions.bot ()) @@ -1133,7 +1133,7 @@ struct let rec project p (v: t): t = match v with | `Int n -> `Int (ID.project p n) - | `Float n -> failwith "todo" + | `Float n -> failwith "todo - project" | `Address n -> `Address (project_addr p n) | `Struct n -> `Struct (Structs.map (fun (x: t) -> project p x) n) | `Union (f, v) -> `Union (f, project p v) diff --git a/tests/regression/56-floats/01-base.c b/tests/regression/56-floats/01-base.c index f73cfbd9fc..07e3adc7b3 100644 --- a/tests/regression/56-floats/01-base.c +++ b/tests/regression/56-floats/01-base.c @@ -6,12 +6,19 @@ int main() { double middle; - double a = DBL_MAX; - double b = DBL_MAX; + double a = 2.; + double b = 4.; - middle = middle * 1.; // of course all accesses before initialization should result in a warning + assert(middle == 2.); // UNKNOWN! + + assert(a == 2.); // SUCCESS! + assert(a < 10.); // SUCCESS! + assert(a > 10.); // FAIL! middle = (a + b) / 2.; // naive way of computing the middle - return middle - 100.; // further uses of the overflown middle value should result in a warning + assert(middle == 3.); // SUCCESS! + + assert(-97. == middle - 100.); + return 0; } From ffd15387844b83c5d6536e5745de60ecdc08a782 Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Thu, 12 May 2022 20:20:19 +0200 Subject: [PATCH 03/54] Introduce new ana.float.interval option --- src/cdomains/intDomain.ml | 14 +++++++------- src/cdomains/intDomain.mli | 4 ++-- src/cdomains/valueDomain.ml | 2 +- src/util/options.schema.json | 14 ++++++++++++++ src/util/precisionUtil.ml | 18 +++++++++++++----- tests/regression/56-floats/01-base.c | 1 + 6 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/cdomains/intDomain.ml b/src/cdomains/intDomain.ml index d0e18db891..e20e0569f7 100644 --- a/src/cdomains/intDomain.ml +++ b/src/cdomains/intDomain.ml @@ -162,7 +162,7 @@ sig val refine_with_excl_list: Cil.ikind -> t -> (int_t list * (int64 * int64)) option -> t val refine_with_incl_list: Cil.ikind -> t -> int_t list option -> t - val project: Cil.ikind -> precision -> t -> t + val project: Cil.ikind -> int_precision -> t -> t val arbitrary: Cil.ikind -> t QCheck.arbitrary end (** Interface of IntDomain implementations taking an ikind for arithmetic operations *) @@ -181,7 +181,7 @@ sig val ending : Cil.ikind -> int_t -> t val is_top_of: Cil.ikind -> t -> bool - val project: precision -> t -> t + val project: int_precision -> t -> t end module type Z = Y with type int_t = BI.t @@ -2529,16 +2529,16 @@ module IntDomTupleImpl = struct type poly1 = {f1: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a} (* needed b/c above 'b must be different from 'a *) type poly2 = {f2: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a -> 'a} type 'b poly3 = { f3: 'a. 'a m -> 'a option } (* used for projection to given precision *) - let create r x ((p1, p2, p3, p4): precision) = + let create r x ((p1, p2, p3, p4): int_precision) = let f b g = if b then Some (g x) else None in f p1 @@ r.fi (module I1), f p2 @@ r.fi (module I2), f p3 @@ r.fi (module I3), f p4 @@ r.fi (module I4) let create r x = (* use where values are introduced *) - create r x (precision_from_node_or_config ()) - let create2 r x ((p1, p2, p3, p4): precision) = + create r x (int_precision_from_node_or_config ()) + let create2 r x ((p1, p2, p3, p4): int_precision) = let f b g = if b then Some (g x) else None in f p1 @@ r.fi2 (module I1), f p2 @@ r.fi2 (module I2), f p3 @@ r.fi2 (module I3), f p4 @@ r.fi2 (module I4) let create2 r x = (* use where values are introduced *) - create2 r x (precision_from_node_or_config ()) + create2 r x (int_precision_from_node_or_config ()) let opt_map2 f ?no_ov = curry @@ function Some x, Some y -> Some (f ?no_ov x y) | _ -> None @@ -2806,7 +2806,7 @@ module IntDomTupleImpl = struct * ~keep:true will keep elements that are `Some x` but should be set to `None` by p. * This way we won't loose any information for the refinement. * ~keep:false will set the elements to `None` as defined by p *) - let project ik (p: precision) t = + let project ik (p: int_precision) t = let t_padded = map ~keep:true { f3 = fun (type a) (module I:S with type t = a) -> Some (I.top_of ik) } t p in let t_refined = refine ik t_padded in map ~keep:false { f3 = fun (type a) (module I:S with type t = a) -> None } t_refined p diff --git a/src/cdomains/intDomain.mli b/src/cdomains/intDomain.mli index 1a5756f5c9..6e37161722 100644 --- a/src/cdomains/intDomain.mli +++ b/src/cdomains/intDomain.mli @@ -274,7 +274,7 @@ sig val refine_with_excl_list: Cil.ikind -> t -> (int_t list * (int64 * int64)) option -> t val refine_with_incl_list: Cil.ikind -> t -> int_t list option -> t - val project: Cil.ikind -> PrecisionUtil.precision -> t -> t + val project: Cil.ikind -> PrecisionUtil.int_precision -> t -> t val arbitrary: Cil.ikind -> t QCheck.arbitrary end (** Interface of IntDomain implementations taking an ikind for arithmetic operations *) @@ -303,7 +303,7 @@ sig val is_top_of: Cil.ikind -> t -> bool - val project: PrecisionUtil.precision -> t -> t + val project: PrecisionUtil.int_precision -> t -> t end (** The signature of integral value domains keeping track of ikind information *) diff --git a/src/cdomains/valueDomain.ml b/src/cdomains/valueDomain.ml index 9a3bef4cf1..2837d4f5e6 100644 --- a/src/cdomains/valueDomain.ml +++ b/src/cdomains/valueDomain.ml @@ -36,7 +36,7 @@ sig val is_top_value: t -> typ -> bool val zero_init_value: typ -> t - val project: precision -> t -> t + val project: int_precision -> t -> t end module type Blob = diff --git a/src/util/options.schema.json b/src/util/options.schema.json index 67bf49e3b0..fa92b6e0ff 100644 --- a/src/util/options.schema.json +++ b/src/util/options.schema.json @@ -511,6 +511,20 @@ }, "additionalProperties": false }, + "float": { + "title": "ana.float", + "type": "object", + "properties": { + "interval": { + "title": "ana.float.interval", + "description": + "Use FloatDomain: (float * float) option.", + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + }, "file": { "title": "ana.file", "type": "object", diff --git a/src/util/precisionUtil.ml b/src/util/precisionUtil.ml index 2e07c6cba0..dda474b74e 100644 --- a/src/util/precisionUtil.ml +++ b/src/util/precisionUtil.ml @@ -1,23 +1,31 @@ (* We define precision by the number of IntDomains activated. * We currently have 4 types: DefExc, Interval, Enums, Congruence *) -type precision = (bool * bool * bool * bool) +type int_precision = (bool * bool * bool * bool) +(* Same applies for FloatDomain + * We currently have only an interval type analysis *) +type float_precision = (bool) (* Thus for maximum precision we activate all IntDomains *) -let max_precision : precision = (true, true, true, true) -let precision_from_fundec (fd: Cil.fundec): precision = +let max_precision : int_precision = (true, true, true, true) +let precision_from_fundec (fd: Cil.fundec): int_precision = ((ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.def_exc" ~removeAttr:"no-def_exc" ~keepAttr:"def_exc" fd), (ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.interval" ~removeAttr:"no-interval" ~keepAttr:"interval" fd), (ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.enums" ~removeAttr:"no-enums" ~keepAttr:"enums" fd), (ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.congruence" ~removeAttr:"no-congruence" ~keepAttr:"congruence" fd)) -let precision_from_node (): precision = +let precision_from_node (): int_precision = match !MyCFG.current_node with | Some n -> precision_from_fundec (Node.find_fundec n) | _ -> max_precision (* In case a Node is None we have to handle Globals, i.e. we activate all IntDomains (TODO: verify this assumption) *) -let precision_from_node_or_config (): precision = +let int_precision_from_node_or_config (): int_precision = if GobConfig.get_bool "annotation.int.enabled" then precision_from_node () else let f n = GobConfig.get_bool ("ana.int."^n) in (f "def_exc", f "interval", f "enums", f "congruence") + +let float_precision_from_node_or_config (): float_precision = + (* TODO(Practical2022): allow partital evaluation by enabling/disabling our analysis on a more finegrained level *) + let f n = GobConfig.get_bool ("ana.float."^n) in + (f "interval") diff --git a/tests/regression/56-floats/01-base.c b/tests/regression/56-floats/01-base.c index 07e3adc7b3..199310d998 100644 --- a/tests/regression/56-floats/01-base.c +++ b/tests/regression/56-floats/01-base.c @@ -1,3 +1,4 @@ +// PARAM: --enable ana.float.interval #include #include #include From 8808ced86a28a2d258ed927f9bfc4b28b60a549c Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Thu, 12 May 2022 20:49:02 +0200 Subject: [PATCH 04/54] Add wrapper domain for Floating point in general --- src/cdomains/floatDomain.ml | 187 ++++++++++++++++++++++++++++----- src/cdomains/floatDomain.mli | 75 +++++++------ src/cdomains/preValueDomain.ml | 2 +- 3 files changed, 198 insertions(+), 66 deletions(-) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index d75ee96bb2..7ce8581b78 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -1,4 +1,42 @@ +open GobConfig open Pretty +open PrecisionUtil + +module type FloatArith = sig + type t + + val neg : t -> t + (** Negating an flaot value: [-x] *) + val add : t -> t -> t + (** Addition: [x + y] *) + val sub : t -> t -> t + (** Subtraction: [x - y] *) + val mul : t -> t -> t + (** Multiplication: [x * y] *) + val div : t -> t -> t + (** Division: [x / y] *) + + (** {b Comparison operators} *) + val lt : t -> t -> IntDomain.IntDomTuple.t + (** Less than: [x < y] *) + val gt : t -> t -> IntDomain.IntDomTuple.t + (** Greater than: [x > y] *) + val le : t -> t -> IntDomain.IntDomTuple.t + (** Less than or equal: [x <= y] *) + val ge : t -> t -> IntDomain.IntDomTuple.t + (** Greater than or equal: [x >= y] *) + val eq : t -> t -> IntDomain.IntDomTuple.t + (** Equal to: [x == y] *) + val ne : t -> t -> IntDomain.IntDomTuple.t + (** Not equal to: [x != y] *) +end + +module type FloatDomainBase = sig + include Lattice.S + include FloatArith with type t := t + + val of_const : float -> t +end let eval_binop operation op1 op2 = match (op1, op2) with Some v1, Some v2 -> operation v1 v2 | _ -> None @@ -11,7 +49,6 @@ let eval_int_binop operation op1 op2 = (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) let add (low1, high1) (low2, high2) = Some (low1 +. low2, high1 +. high2) - let sub (low1, high1) (low2, high2) = Some (low1 -. low2, high1 -. high2) let mul (low1, high1) (low2, high2) = @@ -54,16 +91,13 @@ module FloatDomTuple = struct type t = (float * float) option [@@deriving eq, to_yojson] let of_const f = Some (f, f) - let hash = Hashtbl.hash - let compare _ _ = failwith "todo" let show = function - | None -> - "Float [arbitrary]" + | None -> "Float [arbitrary]" | Some (low, high) -> - "Float [" ^ string_of_float low ^ "," ^ string_of_float high ^ "]" + "Float [" ^ string_of_float low ^ "," ^ string_of_float high ^ "]" let pretty () x = text (show x) @@ -71,19 +105,12 @@ module FloatDomTuple = struct BatPrintf.fprintf f "\n\n%s\n\n\n" (show x) let name () = "float interval domain" - let invariant _ (x : t) = failwith "todo" - let tag (x : t) = failwith "todo" - let arbitrary () = failwith "todo" - let relift (x : t) = failwith "todo" - let leq _ _ = failwith "todo" - let join _ _ = failwith "todo" - let meet _ _ = failwith "todo" (** [widen x y] assumes [leq x y]. Solvers guarantee this by calling [widen old (join old new)]. *) @@ -96,32 +123,138 @@ module FloatDomTuple = struct Pretty.dprintf "%a instead of %a" pretty x pretty y let bot () = failwith "no bot exists" - let is_bot _ = failwith "no bot exists" - let top () = None - let is_top = Option.is_none - let neg = Option.map (fun (low, high) -> (-.high, -.low)) - let add = eval_binop add - let sub = eval_binop sub - let mul = eval_binop mul - let div = eval_binop div - let lt = eval_int_binop lt - let gt = eval_int_binop gt - let le _ _ = failwith "todo - le" - let ge _ _ = failwith "todo - ge" - let eq = eval_int_binop eq - let ne = eval_int_binop ne end + +module FloatDomainImpl = struct + include Printable.Std (* for default invariant, tag, ... *) + module F1 = FloatDomTuple + open Batteries + + type t = F1.t option [@@deriving to_yojson, eq, ord] + + let name () = "floatdomtuple" + + type 'a m = (module FloatDomainBase with type t = 'a) + (* only first-order polymorphism on functions + -> use records to get around monomorphism restriction on arguments (Same trick as used in intDomain) *) + type 'b poly_in = { fi : 'a. 'a m -> 'b -> 'a } + type 'b poly_pr = { fp : 'a. 'a m -> 'a -> 'b } + type 'b poly2_pr = { f2p : 'a. 'a m -> 'a -> 'a -> 'b } + type poly1 = { f1 : 'a. 'a m -> 'a -> 'a } + type poly2 = { f2 : 'a. 'a m -> 'a -> 'a -> 'a } + + let create r x (f1 : float_precision) = + let f b g = if b then Some (g x) else None in + f f1 @@ r.fi (module F1) + + let create r x = + (* use where values are introduced *) + create r x (float_precision_from_node_or_config ()) + + let opt_map2 f = + curry @@ function Some x, Some y -> Some (f x y) | _ -> None + + let exists = function Some true -> true | _ -> false + let for_all = function Some false -> false | _ -> true + + let mapp r = BatOption.map (r.fp (module F1)) + + let map r a = BatOption.(map (r.f1 (module F1)) a) + let map2 r xa ya = opt_map2 (r.f2 (module F1)) xa ya + let map2p r xa ya = opt_map2 (r.f2p (module F1)) xa ya + + let map2int r xa ya = + Option.map_default identity + (IntDomain.IntDomTuple.top_of IBool) (opt_map2 (r.f2p (module F1)) xa ya) + + let ( %% ) f g x = f % g x + + let show x = + Option.map_default identity "" + (mapp { fp= (fun (type a) (module F : FloatDomainBase with type t = a) x -> F.name () ^ ":" ^ F.show x); } x) + + let to_yojson = + [%to_yojson: Yojson.Safe.t option] + % mapp { fp= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.to_yojson); } + let hash x = + Option.map_default identity 0 + (mapp { fp= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.hash); } x) + + let of_const = + create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.of_const); } + + let top = + create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.top); } + let bot = + create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.bot); } + let is_bot = + exists + % mapp { fp= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.is_bot); } + let is_top = + for_all + % mapp { fp= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.is_top); } + + + let leq = + for_all + %% map2p { f2p= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.leq); } + + let pretty () x = + Option.map_default identity nil + (mapp { fp= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.pretty ()); } x) + + (* f1: one and only unary op *) + let neg = + map { f1= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.neg); } + + (* f2: binary ops *) + let join = + map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.join); } + let meet = + map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.meet); } + let widen = + map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.widen); } + let narrow = + map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.narrow); } + let add = + map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.add); } + let sub = + map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.sub); } + let mul = + map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.mul); } + let div = + map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.div); } + + (* f2: binary ops which return an integer *) + let lt = + map2int { f2p= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.lt); } + let gt = + map2int { f2p= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.gt); } + let le = + map2int { f2p= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.le); } + let ge = + map2int { f2p= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.ge); } + let eq = + map2int { f2p= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.eq); } + let ne = + map2int { f2p= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.ne); } + + let pretty_diff () (x, y) = dprintf "%a instead of %a" pretty x pretty y + + let printXml f x = + BatPrintf.fprintf f "\n\n%s\n\n\n" (show x) +end diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index 4f47d265f4..22825c9509 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -1,44 +1,43 @@ (** Abstract Domains for floats. These are domains that support the C * operations on double/float values. *) - module FloatDomTuple : sig - include Lattice.S +module type FloatArith = sig + type t - val of_const: float -> t + val neg : t -> t + (** Negating an flaot value: [-x] *) + val add : t -> t -> t + (** Addition: [x + y] *) + val sub : t -> t -> t + (** Subtraction: [x - y] *) + val mul : t -> t -> t + (** Multiplication: [x * y] *) + val div : t -> t -> t + (** Division: [x / y] *) - val neg: t -> t - (** Negating an flaot value: [-x] *) - - val add: t -> t -> t - (** Addition: [x + y] *) - - val sub: t -> t -> t - (** Subtraction: [x - y] *) - - val mul: t -> t -> t - (** Multiplication: [x * y] *) - - val div: t -> t -> t - (** Division: [x / y] *) - - (** {b Comparison operators} *) - - val lt: t -> t -> IntDomain.IntDomTuple.t - (** Less than: [x < y] *) - - val gt: t -> t -> IntDomain.IntDomTuple.t - (** Greater than: [x > y] *) - - val le: t -> t -> IntDomain.IntDomTuple.t - (** Less than or equal: [x <= y] *) - - val ge: t -> t -> IntDomain.IntDomTuple.t - (** Greater than or equal: [x >= y] *) - - val eq: t -> t -> IntDomain.IntDomTuple.t - (** Equal to: [x == y] *) - - val ne: t -> t -> IntDomain.IntDomTuple.t - (** Not equal to: [x != y] *) - end + (** {b Comparison operators} *) + val lt : t -> t -> IntDomain.IntDomTuple.t + (** Less than: [x < y] *) + val gt : t -> t -> IntDomain.IntDomTuple.t + (** Greater than: [x > y] *) + val le : t -> t -> IntDomain.IntDomTuple.t + (** Less than or equal: [x <= y] *) + val ge : t -> t -> IntDomain.IntDomTuple.t + (** Greater than or equal: [x >= y] *) + val eq : t -> t -> IntDomain.IntDomTuple.t + (** Equal to: [x == y] *) + val ne : t -> t -> IntDomain.IntDomTuple.t + (** Not equal to: [x != y] *) +end +module type FloatDomainBase = sig + include Lattice.S + + include FloatArith with type t := t + + val of_const : float -> t +end + +module FloatDomainImpl : sig + include FloatDomainBase +end diff --git a/src/cdomains/preValueDomain.ml b/src/cdomains/preValueDomain.ml index 594241973d..d48369fc79 100644 --- a/src/cdomains/preValueDomain.ml +++ b/src/cdomains/preValueDomain.ml @@ -1,5 +1,5 @@ module ID = IntDomain.IntDomTuple -module FD = FloatDomain.FloatDomTuple +module FD = FloatDomain.FloatDomainImpl module IndexDomain = IntDomain.IntDomWithDefaultIkind (ID) (IntDomain.PtrDiffIkind) module AD = AddressDomain.AddressSet (IndexDomain) module Addr = Lval.NormalLat (IndexDomain) From e11c846257c338f6d57ec88fc63b3c33462e0f8c Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Sun, 15 May 2022 12:15:16 +0200 Subject: [PATCH 05/54] Apply different renamings and undo formatting inside FloatInterval --- src/analyses/base.ml | 4 ++-- src/cdomains/floatDomain.ml | 39 ++++++++++++++++++++++++++++------ src/cdomains/floatDomain.mli | 2 +- src/cdomains/preValueDomain.ml | 2 +- src/util/precisionUtil.ml | 10 ++++----- 5 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 5f70db9003..0e92d31933 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -2007,7 +2007,7 @@ struct let new_cpa = CPA.add_list_fun reachable (fun v -> CPA.find v st.cpa) new_cpa in (* Projection to Precision of the Callee *) - let p = PU.precision_from_fundec fundec in + let p = PU.int_precision_from_fundec fundec in let new_cpa = project (Some p) new_cpa in (* Identify locals of this fundec for which an outer copy (from a call down the callstack) is reachable *) @@ -2448,7 +2448,7 @@ struct let nst = add_globals st fun_st in (* Projection to Precision of the Caller *) - let p = PrecisionUtil.precision_from_node () in (* Since f is the fundec of the Callee we have to get the fundec of the current Node instead *) + let p = PrecisionUtil.int_precision_from_node () in (* Since f is the fundec of the Callee we have to get the fundec of the current Node instead *) let return_val = project_val (Some p) return_val (is_privglob (return_varinfo ())) in let cpa' = project (Some p) nst.cpa in diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 7ce8581b78..834bc01dba 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -49,6 +49,7 @@ let eval_int_binop operation op1 op2 = (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) let add (low1, high1) (low2, high2) = Some (low1 +. low2, high1 +. high2) + let sub (low1, high1) (low2, high2) = Some (low1 -. low2, high1 -. high2) let mul (low1, high1) (low2, high2) = @@ -87,17 +88,20 @@ let lt (low1, high1) (low2, high2) = let gt (low1, high1) (low2, high2) = if low1 > high2 then (1, 1) else if high1 < low2 then (0, 0) else (0, 1) -module FloatDomTuple = struct +module FloatInterval = struct type t = (float * float) option [@@deriving eq, to_yojson] let of_const f = Some (f, f) + let hash = Hashtbl.hash + let compare _ _ = failwith "todo" let show = function - | None -> "Float [arbitrary]" + | None -> + "Float [arbitrary]" | Some (low, high) -> - "Float [" ^ string_of_float low ^ "," ^ string_of_float high ^ "]" + "Float [" ^ string_of_float low ^ "," ^ string_of_float high ^ "]" let pretty () x = text (show x) @@ -105,12 +109,19 @@ module FloatDomTuple = struct BatPrintf.fprintf f "\n\n%s\n\n\n" (show x) let name () = "float interval domain" + let invariant _ (x : t) = failwith "todo" + let tag (x : t) = failwith "todo" + let arbitrary () = failwith "todo" + let relift (x : t) = failwith "todo" + let leq _ _ = failwith "todo" + let join _ _ = failwith "todo" + let meet _ _ = failwith "todo" (** [widen x y] assumes [leq x y]. Solvers guarantee this by calling [widen old (join old new)]. *) @@ -123,30 +134,44 @@ module FloatDomTuple = struct Pretty.dprintf "%a instead of %a" pretty x pretty y let bot () = failwith "no bot exists" + let is_bot _ = failwith "no bot exists" + let top () = None + let is_top = Option.is_none + let neg = Option.map (fun (low, high) -> (-.high, -.low)) + let add = eval_binop add + let sub = eval_binop sub + let mul = eval_binop mul + let div = eval_binop div + let lt = eval_int_binop lt + let gt = eval_int_binop gt + let le _ _ = failwith "todo - le" + let ge _ _ = failwith "todo - ge" + let eq = eval_int_binop eq + let ne = eval_int_binop ne end -module FloatDomainImpl = struct +module FloatDomImpl = struct include Printable.Std (* for default invariant, tag, ... *) - module F1 = FloatDomTuple + module F1 = FloatInterval open Batteries type t = F1.t option [@@deriving to_yojson, eq, ord] - let name () = "floatdomtuple" + let name () = "FloatInterval" type 'a m = (module FloatDomainBase with type t = 'a) (* only first-order polymorphism on functions @@ -163,7 +188,7 @@ module FloatDomainImpl = struct let create r x = (* use where values are introduced *) - create r x (float_precision_from_node_or_config ()) + create r x (float_precision_from_config ()) let opt_map2 f = curry @@ function Some x, Some y -> Some (f x y) | _ -> None diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index 22825c9509..dc97753bba 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -38,6 +38,6 @@ module type FloatDomainBase = sig val of_const : float -> t end -module FloatDomainImpl : sig +module FloatDomImpl : sig include FloatDomainBase end diff --git a/src/cdomains/preValueDomain.ml b/src/cdomains/preValueDomain.ml index d48369fc79..a2fe7b4788 100644 --- a/src/cdomains/preValueDomain.ml +++ b/src/cdomains/preValueDomain.ml @@ -1,5 +1,5 @@ module ID = IntDomain.IntDomTuple -module FD = FloatDomain.FloatDomainImpl +module FD = FloatDomain.FloatDomImpl module IndexDomain = IntDomain.IntDomWithDefaultIkind (ID) (IntDomain.PtrDiffIkind) module AD = AddressDomain.AddressSet (IndexDomain) module Addr = Lval.NormalLat (IndexDomain) diff --git a/src/util/precisionUtil.ml b/src/util/precisionUtil.ml index dda474b74e..e4364284b4 100644 --- a/src/util/precisionUtil.ml +++ b/src/util/precisionUtil.ml @@ -8,24 +8,24 @@ type float_precision = (bool) (* Thus for maximum precision we activate all IntDomains *) let max_precision : int_precision = (true, true, true, true) -let precision_from_fundec (fd: Cil.fundec): int_precision = +let int_precision_from_fundec (fd: Cil.fundec): int_precision = ((ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.def_exc" ~removeAttr:"no-def_exc" ~keepAttr:"def_exc" fd), (ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.interval" ~removeAttr:"no-interval" ~keepAttr:"interval" fd), (ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.enums" ~removeAttr:"no-enums" ~keepAttr:"enums" fd), (ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.congruence" ~removeAttr:"no-congruence" ~keepAttr:"congruence" fd)) -let precision_from_node (): int_precision = +let int_precision_from_node (): int_precision = match !MyCFG.current_node with - | Some n -> precision_from_fundec (Node.find_fundec n) + | Some n -> int_precision_from_fundec (Node.find_fundec n) | _ -> max_precision (* In case a Node is None we have to handle Globals, i.e. we activate all IntDomains (TODO: verify this assumption) *) let int_precision_from_node_or_config (): int_precision = if GobConfig.get_bool "annotation.int.enabled" then - precision_from_node () + int_precision_from_node () else let f n = GobConfig.get_bool ("ana.int."^n) in (f "def_exc", f "interval", f "enums", f "congruence") -let float_precision_from_node_or_config (): float_precision = +let float_precision_from_config (): float_precision = (* TODO(Practical2022): allow partital evaluation by enabling/disabling our analysis on a more finegrained level *) let f n = GobConfig.get_bool ("ana.float."^n) in (f "interval") From 47fd747873e51857be646e2816073ea4126a5a53 Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Tue, 17 May 2022 13:32:04 +0200 Subject: [PATCH 06/54] Fix rename and unused import --- src/cdomains/floatDomain.ml | 5 ++--- src/cdomains/floatDomain.mli | 2 +- src/cdomains/preValueDomain.ml | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 834bc01dba..c6ec061c4e 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -1,4 +1,3 @@ -open GobConfig open Pretty open PrecisionUtil @@ -164,14 +163,14 @@ module FloatInterval = struct let ne = eval_int_binop ne end -module FloatDomImpl = struct +module FloatDomTupleImpl = struct include Printable.Std (* for default invariant, tag, ... *) module F1 = FloatInterval open Batteries type t = F1.t option [@@deriving to_yojson, eq, ord] - let name () = "FloatInterval" + let name () = "floatdomtuple" type 'a m = (module FloatDomainBase with type t = 'a) (* only first-order polymorphism on functions diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index dc97753bba..95afa634bb 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -38,6 +38,6 @@ module type FloatDomainBase = sig val of_const : float -> t end -module FloatDomImpl : sig +module FloatDomTupleImpl : sig include FloatDomainBase end diff --git a/src/cdomains/preValueDomain.ml b/src/cdomains/preValueDomain.ml index a2fe7b4788..6097778ecb 100644 --- a/src/cdomains/preValueDomain.ml +++ b/src/cdomains/preValueDomain.ml @@ -1,5 +1,5 @@ module ID = IntDomain.IntDomTuple -module FD = FloatDomain.FloatDomImpl +module FD = FloatDomain.FloatDomTupleImpl module IndexDomain = IntDomain.IntDomWithDefaultIkind (ID) (IntDomain.PtrDiffIkind) module AD = AddressDomain.AddressSet (IndexDomain) module Addr = Lval.NormalLat (IndexDomain) From fad20372676e3a7bebb51beb5db1777ae90160ce Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Wed, 18 May 2022 10:06:17 +0200 Subject: [PATCH 07/54] Add node-level configurability of float analysis --- src/analyses/base.ml | 2 +- src/cdomains/floatDomain.ml | 2 +- src/util/options.schema.json | 14 +++++++++++ src/util/precisionUtil.ml | 25 +++++++++++++------ .../56-floats/02-node_configuration.c | 19 ++++++++++++++ 5 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 tests/regression/56-floats/02-node_configuration.c diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 0e92d31933..cb5b03e6d9 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -85,7 +85,7 @@ struct let project_val p_opt value is_glob = match GobConfig.get_bool "annotation.int.enabled", is_glob, p_opt with - | true, true, _ -> VD.project PU.max_precision value + | true, true, _ -> VD.project PU.max_int_precision value | true, false, Some p -> VD.project p value | _ -> value diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index c6ec061c4e..66ba51b86a 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -187,7 +187,7 @@ module FloatDomTupleImpl = struct let create r x = (* use where values are introduced *) - create r x (float_precision_from_config ()) + create r x (float_precision_from_node_or_config ()) let opt_map2 f = curry @@ function Some x, Some y -> Some (f x y) | _ -> None diff --git a/src/util/options.schema.json b/src/util/options.schema.json index fa92b6e0ff..dd39e931ea 100644 --- a/src/util/options.schema.json +++ b/src/util/options.schema.json @@ -1229,6 +1229,20 @@ }, "additionalProperties": false }, + "float": { + "title": "annotation.float", + "type": "object", + "properties": { + "enabled": { + "title": "annotation.float.enabled", + "description": + "Enable manual annotation of functions with desired precision, i.e. the activated FloatDomains.", + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + }, "goblint_context": { "title": "annotation.goblint_context", "type": "object", diff --git a/src/util/precisionUtil.ml b/src/util/precisionUtil.ml index e4364284b4..9d45110dfe 100644 --- a/src/util/precisionUtil.ml +++ b/src/util/precisionUtil.ml @@ -6,17 +6,26 @@ type int_precision = (bool * bool * bool * bool) type float_precision = (bool) -(* Thus for maximum precision we activate all IntDomains *) -let max_precision : int_precision = (true, true, true, true) +(* Thus for maximum precision we activate all Domains *) +let max_int_precision : int_precision = (true, true, true, true) +let max_float_precision : float_precision = (true) let int_precision_from_fundec (fd: Cil.fundec): int_precision = ((ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.def_exc" ~removeAttr:"no-def_exc" ~keepAttr:"def_exc" fd), (ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.interval" ~removeAttr:"no-interval" ~keepAttr:"interval" fd), (ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.enums" ~removeAttr:"no-enums" ~keepAttr:"enums" fd), (ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.congruence" ~removeAttr:"no-congruence" ~keepAttr:"congruence" fd)) + + let float_precision_from_fundec (fd: Cil.fundec): float_precision = + ((ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.float.interval" ~removeAttr:"no-float-interval" ~keepAttr:"float-interval" fd)) let int_precision_from_node (): int_precision = match !MyCFG.current_node with | Some n -> int_precision_from_fundec (Node.find_fundec n) - | _ -> max_precision (* In case a Node is None we have to handle Globals, i.e. we activate all IntDomains (TODO: verify this assumption) *) + | _ -> max_int_precision (* In case a Node is None we have to handle Globals, i.e. we activate all IntDomains (TODO: verify this assumption) *) + + let float_precision_from_node (): float_precision = + match !MyCFG.current_node with + | Some n -> float_precision_from_fundec (Node.find_fundec n) + | _ -> max_float_precision let int_precision_from_node_or_config (): int_precision = if GobConfig.get_bool "annotation.int.enabled" then @@ -25,7 +34,9 @@ let int_precision_from_node_or_config (): int_precision = let f n = GobConfig.get_bool ("ana.int."^n) in (f "def_exc", f "interval", f "enums", f "congruence") -let float_precision_from_config (): float_precision = - (* TODO(Practical2022): allow partital evaluation by enabling/disabling our analysis on a more finegrained level *) - let f n = GobConfig.get_bool ("ana.float."^n) in - (f "interval") +let float_precision_from_node_or_config (): float_precision = + if GobConfig.get_bool "annotation.float.enabled" then + float_precision_from_node () + else + let f n = GobConfig.get_bool ("ana.float."^n) in + (f "interval") diff --git a/tests/regression/56-floats/02-node_configuration.c b/tests/regression/56-floats/02-node_configuration.c new file mode 100644 index 0000000000..a05268836a --- /dev/null +++ b/tests/regression/56-floats/02-node_configuration.c @@ -0,0 +1,19 @@ +// PARAM: --enable annotation.float.enabled +#include + +int main() __attribute__((goblint_precision("no-float-interval"))); +void test() __attribute__((goblint_precision("float-interval"))); + +int main() +{ + double a = 2.; + assert(a == 2.); // UNKNOWN! + test(); + return 0; +} + +void test() +{ + double b = 2.; + assert(b == 2.); // SUCCESS! +} From e8c496d066ecb440546548d39e0f517185c87325 Mon Sep 17 00:00:00 2001 From: FelixKrayer Date: Sun, 15 May 2022 22:08:42 +0200 Subject: [PATCH 08/54] most functionality implemented - no unittests yet, only framework! --- dune-project | 1 + goblint.opam | 2 + goblint.opam.locked | 5 + goblint.opam.template | 1 + src/cdomains/floatDomain.ml | 245 +++++++++++++++++---------- src/dune | 2 +- unittest/cdomains/floatDomainTest.ml | 22 +++ unittest/mainTest.ml | 5 +- 8 files changed, 192 insertions(+), 91 deletions(-) create mode 100644 unittest/cdomains/floatDomainTest.ml diff --git a/dune-project b/dune-project index c142b805c7..d4b8137414 100644 --- a/dune-project +++ b/dune-project @@ -34,6 +34,7 @@ ppx_deriving_hash ppx_deriving_yojson (ppx_blob (>= 0.6.0)) + round (ocaml-monadic (>= 0.5)) (ounit2 :with-test) (qcheck-ounit :with-test) diff --git a/goblint.opam b/goblint.opam index 32260d4324..3383ecea3a 100644 --- a/goblint.opam +++ b/goblint.opam @@ -30,6 +30,7 @@ depends: [ "ppx_deriving_hash" "ppx_deriving_yojson" "ppx_blob" {>= "0.6.0"} + "round" "ocaml-monadic" {>= "0.5"} "ounit2" {with-test} "qcheck-ounit" {with-test} @@ -73,4 +74,5 @@ pin-depends: [ # [ "ppx_deriving_yojson.3.6.1" "git+https://github.com/ocaml-ppx/ppx_deriving_yojson.git#e030f13a3450e9cf7d2c43fa04e709ef608486cd" ] # TODO: add back after release, only pinned for CI stability [ "apron.v0.9.13" "git+https://github.com/antoinemine/apron.git#c852ebcc89e5cf4a5a3318e7c13c73e1756abb11"] + [ "round.0.1" "git+https://github.com/brgr/ocaml-round.git#2dfe43050bfdac885607b6697c19c2ed913d5be4"] ] diff --git a/goblint.opam.locked b/goblint.opam.locked index 4ae124b8cc..8c5050fac1 100644 --- a/goblint.opam.locked +++ b/goblint.opam.locked @@ -80,6 +80,7 @@ depends: [ "qcheck-ounit" {= "0.18.1" & with-test} "re" {= "1.10.3" & with-doc} "result" {= "1.5"} + "round" {= "0.1"} "seq" {= "base" & with-test} "sexplib0" {= "v0.14.0"} "sha" {= "1.15.2"} @@ -126,4 +127,8 @@ pin-depends: [ "ppx_deriving.5.2.1" "git+https://github.com/ocaml-ppx/ppx_deriving.git#0a89b619f94cbbfc3b0fb3255ab4fe5bc77d32d6" ] + [ + "round.0.1" + "git+https://github.com/brgr/ocaml-round.git#2dfe43050bfdac885607b6697c19c2ed913d5be4" + ] ] diff --git a/goblint.opam.template b/goblint.opam.template index 5813c9f0ae..004b35f800 100644 --- a/goblint.opam.template +++ b/goblint.opam.template @@ -8,4 +8,5 @@ pin-depends: [ # [ "ppx_deriving_yojson.3.6.1" "git+https://github.com/ocaml-ppx/ppx_deriving_yojson.git#e030f13a3450e9cf7d2c43fa04e709ef608486cd" ] # TODO: add back after release, only pinned for CI stability [ "apron.v0.9.13" "git+https://github.com/antoinemine/apron.git#c852ebcc89e5cf4a5a3318e7c13c73e1756abb11"] + [ "round.0.1" "git+https://github.com/brgr/ocaml-round.git#2dfe43050bfdac885607b6697c19c2ed913d5be4"] ] diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 66ba51b86a..6ded2e28f7 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -1,5 +1,6 @@ open Pretty open PrecisionUtil +open Round module type FloatArith = sig type t @@ -37,96 +38,72 @@ module type FloatDomainBase = sig val of_const : float -> t end -let eval_binop operation op1 op2 = - match (op1, op2) with Some v1, Some v2 -> operation v1 v2 | _ -> None - -let eval_int_binop operation op1 op2 = - let a, b = - match (op1, op2) with Some v1, Some v2 -> operation v1 v2 | _ -> (0, 1) - in - IntDomain.IntDomTuple.of_interval IBool - (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) - -let add (low1, high1) (low2, high2) = Some (low1 +. low2, high1 +. high2) - -let sub (low1, high1) (low2, high2) = Some (low1 -. low2, high1 -. high2) - -let mul (low1, high1) (low2, high2) = - let mul1 = low1 *. low2 in - let mul2 = low1 *. high2 in - let mul3 = high1 *. low2 in - let mul4 = high1 *. high2 in - let low = min (min (min mul1 mul2) mul3) mul4 in - let high = max (max (max mul1 mul2) mul3) mul4 in - Some (low, high) - -let div (low1, high1) (low2, high2) = - if low2 < 0. && high2 > 0. then None - else - let mul1 = low1 /. low2 in - let mul2 = low1 /. high2 in - let mul3 = high1 /. low2 in - let mul4 = high1 /. high2 in - let low = min (min (min mul1 mul2) mul3) mul4 in - let high = max (max (max mul1 mul2) mul3) mul4 in - Some (low, high) - -let eq (low1, high1) (low2, high2) = - if high1 < low2 || high2 < low1 then (0, 0) - else if high1 = low1 && high2 = low2 && low1 = low2 then (1, 1) - else (0, 1) - -let ne (low1, high1) (low2, high2) = - if high1 < low2 || high2 < low1 then (1, 1) - else if high1 == low1 && high2 == low2 && low1 == low2 then (0, 0) - else (0, 1) - -let lt (low1, high1) (low2, high2) = - if high1 < low2 then (1, 1) else if low1 > high2 then (0, 0) else (0, 1) - -let gt (low1, high1) (low2, high2) = - if low1 > high2 then (1, 1) else if high1 < low2 then (0, 0) else (0, 1) - module FloatInterval = struct - type t = (float * float) option [@@deriving eq, to_yojson] + type t = (float * float) option [@@deriving eq, ord, to_yojson] - let of_const f = Some (f, f) + let norm v = + match v with + | Some (low, high) -> if Float.is_finite low && Float.is_finite high then v else None + | _ -> None - let hash = Hashtbl.hash + let of_const f = norm @@ Some (f, f) - let compare _ _ = failwith "todo" + let hash = Hashtbl.hash let show = function - | None -> - "Float [arbitrary]" - | Some (low, high) -> - "Float [" ^ string_of_float low ^ "," ^ string_of_float high ^ "]" + | None -> "Float[Top]" + | Some (low, high) -> "Float [" ^ string_of_float low ^ "," ^ string_of_float high ^ "]" let pretty () x = text (show x) let printXml f (x : t) = BatPrintf.fprintf f "\n\n%s\n\n\n" (show x) - let name () = "float interval domain" + let name () = "FloatInterval" - let invariant _ (x : t) = failwith "todo" + let invariant _ (x : t) = failwith "todo invariant" - let tag (x : t) = failwith "todo" + let tag (x : t) = failwith "todo tag" (**Quote printable.ml line 24: Unique ID, given by HConsed, for context identification in witness *) - let arbitrary () = failwith "todo" + let arbitrary () = failwith "todo arbitrary" (**for QCheck: should describe how to generate random values and shrink possilbe counter examples *) - let relift (x : t) = failwith "todo" + let relift x = x - let leq _ _ = failwith "todo" + let leq v1 v2 = + match v1, v2 with + | _, None -> true + | None, Some _ -> false + | Some (l1, h1), Some (l2, h2) -> l1 >= l2 && h1 <= l2 - let join _ _ = failwith "todo" + let join v1 v2 = + match v1, v2 with + | None, _ | _, None -> None + | Some (l1, h1), Some (l2, h2) -> Some (min l1 l2, max h1 h2) - let meet _ _ = failwith "todo" + let meet v1 v2 = + match v1, v2 with + | None, v | v, None -> v + | Some (l1, h1), Some (l2, h2) -> + let (l, h) = (max l1 l2, min h1 h2) in + if l <= h + then Some (l, h) + else failwith "meet results in empty Interval" (** [widen x y] assumes [leq x y]. Solvers guarantee this by calling [widen old (join old new)]. *) - let widen _ _ = failwith "todo" - - let narrow _ _ = failwith "todo" + let widen v1 v2 = (**TODO: support 'threshold_widening' option *) + match v1, v2 with + | None, _ | _, None -> None + | Some (l1, h1), Some (l2, h2) -> + (**If we widen and we know that neither interval contains +-inf or nan, it is ok to widen only to +-max_float, + because a widening with +-inf/nan will always result in the case above -> Top *) + let low = if l1 <= l2 then l1 else (-.Float.max_float) in + let high = if h1 >= h2 then h1 else Float.max_float in + norm @@ Some (low, high) + + let narrow v1 v2 = (**TODO: support 'threshold_narrowing' and 'narrow_by_meet' option *) + match v1, v2 with (**we cannot distinguish between the lower bound beeing -inf or the upper bound beeing inf. Also there is nan *) + | None, _ -> v2 + | _, _ -> v1 (** If [leq x y = false], then [pretty_diff () (x, y)] should explain why. *) let pretty_diff () (x, y) = @@ -134,7 +111,7 @@ module FloatInterval = struct let bot () = failwith "no bot exists" - let is_bot _ = failwith "no bot exists" + let is_bot _ = false let top () = None @@ -142,25 +119,115 @@ module FloatInterval = struct let neg = Option.map (fun (low, high) -> (-.high, -.low)) - let add = eval_binop add - - let sub = eval_binop sub - - let mul = eval_binop mul - - let div = eval_binop div - - let lt = eval_int_binop lt - - let gt = eval_int_binop gt - - let le _ _ = failwith "todo - le" - - let ge _ _ = failwith "todo - ge" - - let eq = eval_int_binop eq - - let ne = eval_int_binop ne + let add op1 op2 = + match op1, op2 with + | Some (l1, h1), Some (l2, h2) -> Some (add Down l1 l2, add Up h1 h2) + | _ -> None + + let sub op1 op2 = + match op1, op2 with + | Some (l1, h1), Some (l2, h2) -> norm @@ Some (sub Down l1 l2, sub Up h1 h2) + | _ -> None + + let mul op1 op2 = + match op1, op2 with + | Some (l1, h1), Some (l2, h2) -> + let mul1u = mul Up l1 l2 in + let mul2u = mul Up l1 h2 in + let mul3u = mul Up h1 l2 in + let mul4u = mul Up h1 h2 in + let mul1d = mul Down l1 l2 in + let mul2d = mul Down l1 h2 in + let mul3d = mul Down h1 l2 in + let mul4d = mul Down h1 h2 in + let high = max (max (max mul1u mul2u) mul3u) mul4u in + let low = min (min (min mul1d mul2d) mul3d) mul4d in + norm @@ Some (low, high) + | _ -> None + + let div op1 op2 = + match op1, op2 with + | Some (l1, h1), Some (l2, h2) -> + if l2 <= 0. && h2 >= 0. then None + else + let div1u = div Up l1 l2 in + let div2u = div Up l1 h2 in + let div3u = div Up h1 l2 in + let div4u = div Up h1 h2 in + let div1d = div Down l1 l2 in + let div2d = div Down l1 h2 in + let div3d = div Down h1 l2 in + let div4d = div Down h1 h2 in + let high = max (max (max div1u div2u) div3u) div4u in + let low = min (min (min div1d div2d) div3d) div4d in + norm @@ Some (low, high) + | _ -> None + + let lt op1 op2 = + let (a, b) = + match op1, op2 with + | Some (l1, h1), Some (l2, h2) -> + if h1 < l2 then (1, 1) + else if l1 >= h2 then (0, 0) + else (0, 1) + | _ -> (0, 1) + in + IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) + + let gt op1 op2 = + let (a, b) = + match op1, op2 with + | Some (l1, h1), Some (l2, h2) -> + if l1 > h2 then (1, 1) + else if h1 <= l2 then (0, 0) + else (0, 1) + | _ -> (0, 1) + in + IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) + + let le op1 op2 = + let (a, b) = + match op1, op2 with + | Some (l1, h1), Some (l2, h2) -> + if h1 <= l2 then (1, 1) + else if l1 > h2 then (0, 0) + else (0, 1) + | _ -> (0, 1) + in + IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) + + let ge op1 op2 = + let (a, b) = + match op1, op2 with + | Some (l1, h1), Some (l2, h2) -> + if l1 >= h2 then (1, 1) + else if h1 < l2 then (0, 0) + else (0, 1) + | _ -> (0, 1) + in + IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) + + let eq op1 op2 = + let (a, b) = + match op1, op2 with + | Some (l1, h1), Some (l2, h2) -> + if h1 < l2 || h2 < l1 then (0, 0) + else if h1 = l1 && h2 = l2 && l1 = l2 then (1, 1) + else (0, 1) + | _ -> (0, 1) + in + IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) + + let ne op1 op2 = + let (a, b) = + match op1, op2 with + | Some (l1, h1), Some (l2, h2) -> + if h1 < l2 || h2 < l1 then (1, 1) + else if h1 = l1 && h2 = l2 && l1 = l2 then (0, 0) + else (0, 1) + | _ -> (0, 1) + in + IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) end module FloatDomTupleImpl = struct diff --git a/src/dune b/src/dune index 8b2f7aca8f..38e3708e05 100644 --- a/src/dune +++ b/src/dune @@ -8,7 +8,7 @@ (public_name goblint.lib) (wrapped false) (modules :standard \ goblint mainarinc mainspec privPrecCompare apronPrecCompare messagesCompare) - (libraries goblint.sites goblint-cil.all-features batteries.unthreaded qcheck-core.runner sha json-data-encoding jsonrpc cpu arg-complete fpath + (libraries goblint.sites goblint-cil.all-features batteries.unthreaded qcheck-core.runner sha json-data-encoding jsonrpc cpu arg-complete fpath round ; Conditionally compile based on whether apron optional dependency is installed or not. ; Alternative dependencies seem like the only way to optionally depend on optional dependencies. ; See: https://dune.readthedocs.io/en/stable/concepts.html#alternative-dependencies. diff --git a/unittest/cdomains/floatDomainTest.ml b/unittest/cdomains/floatDomainTest.ml new file mode 100644 index 0000000000..73f4828348 --- /dev/null +++ b/unittest/cdomains/floatDomainTest.ml @@ -0,0 +1,22 @@ +open OUnit2 + +let exampleTest1 _ = + assert_equal 0 0; + assert_equal 1 1 + +module ExampleTestModule2 = +struct + + let exampleTest2 _ = + assert_equal 0 0; + assert_equal 1 1 + + let test () = [ + "exampleTestModule2" >:: exampleTest2; + ] +end + +let test () = "floatDomainTest" >::: + [ "Example Test 1" >:: exampleTest1; + "Example Test 2" >::: ExampleTestModule2.test() + ] \ No newline at end of file diff --git a/unittest/mainTest.ml b/unittest/mainTest.ml index 7eccc3b143..1ac06997d7 100644 --- a/unittest/mainTest.ml +++ b/unittest/mainTest.ml @@ -2,6 +2,7 @@ open OUnit2 let all_tests = ("" >::: [ IntDomainTest.test (); + FloatDomainTest.test (); MapDomainTest.test (); SolverTest.test (); LvalTest.test (); @@ -10,4 +11,6 @@ let all_tests = ("" >::: "domaintest" >::: QCheck_ounit.to_ounit2_test_list Maindomaintest.all_testsuite ]) -let () = run_test_tt_main all_tests +let all_tests_debug = ("" >::: [FloatDomainTest.test ()]) (* TODO: TO BE REMOVED only for personal Debugging *) + +let () = run_test_tt_main all_tests_debug (* TODO: change back to all_tests *) From bd1ef2a962b5a44317afb05b537313a1bba3825e Mon Sep 17 00:00:00 2001 From: FelixKrayer Date: Sat, 21 May 2022 22:57:11 +0200 Subject: [PATCH 09/54] fix leq and add unittests --- src/cdomains/floatDomain.ml | 22 +++++- src/cdomains/floatDomain.mli | 21 ++++++ unittest/cdomains/floatDomainTest.ml | 106 ++++++++++++++++++++++++--- unittest/mainTest.ml | 4 +- 4 files changed, 135 insertions(+), 18 deletions(-) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 6ded2e28f7..85d785ca21 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -43,7 +43,20 @@ module FloatInterval = struct let norm v = match v with - | Some (low, high) -> if Float.is_finite low && Float.is_finite high then v else None + | Some (low, high) -> + if Float.is_finite low && Float.is_finite high then + if low > high then failwith "invalid Interval" + else v + else None + | _ -> None + + (**just for norming the arbitraries, so correct intervals get created, but no failwith if low > high*) + let norm_arb v = + match v with + | Some (f1, f2) -> + if Float.is_finite f1 && Float.is_finite f2 + then Some(min f1 f2, max f1 f2) + else None | _ -> None let of_const f = norm @@ Some (f, f) @@ -65,7 +78,8 @@ module FloatInterval = struct let tag (x : t) = failwith "todo tag" (**Quote printable.ml line 24: Unique ID, given by HConsed, for context identification in witness *) - let arbitrary () = failwith "todo arbitrary" (**for QCheck: should describe how to generate random values and shrink possilbe counter examples *) + (**for QCheck: should describe how to generate random values and shrink possilbe counter examples *) + let arbitrary () = QCheck.map norm_arb (QCheck.option (QCheck.pair QCheck.float QCheck.float)) let relift x = x @@ -73,7 +87,7 @@ module FloatInterval = struct match v1, v2 with | _, None -> true | None, Some _ -> false - | Some (l1, h1), Some (l2, h2) -> l1 >= l2 && h1 <= l2 + | Some (l1, h1), Some (l2, h2) -> l1 >= l2 && h1 <= h2 let join v1 v2 = match v1, v2 with @@ -101,7 +115,7 @@ module FloatInterval = struct norm @@ Some (low, high) let narrow v1 v2 = (**TODO: support 'threshold_narrowing' and 'narrow_by_meet' option *) - match v1, v2 with (**we cannot distinguish between the lower bound beeing -inf or the upper bound beeing inf. Also there is nan *) + match v1, v2 with (**we cannot distinguish between the lower bound beeing -inf or the upper bound beeing inf. Also there is nan *) | None, _ -> v2 | _, _ -> v1 diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index 95afa634bb..bafa52325f 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -30,6 +30,27 @@ module type FloatArith = sig (** Not equal to: [x != y] *) end +module FloatInterval : sig (**Currently just for FloatDomainTest *) + type t = (float * float) option + include FloatArith with type t := t + + val top : unit -> t + + val is_bot : t -> bool + + val is_top : t -> bool + + val of_const : float -> t + + val show : t -> string + + val equal : t -> t -> bool + + val leq : t -> t -> bool + + val arbitrary : unit -> t QCheck.arbitrary +end + module type FloatDomainBase = sig include Lattice.S diff --git a/unittest/cdomains/floatDomainTest.ml b/unittest/cdomains/floatDomainTest.ml index 73f4828348..9d12a2bce8 100644 --- a/unittest/cdomains/floatDomainTest.ml +++ b/unittest/cdomains/floatDomainTest.ml @@ -1,22 +1,106 @@ open OUnit2 +open Round -let exampleTest1 _ = - assert_equal 0 0; - assert_equal 1 1 - -module ExampleTestModule2 = +module FloatInterval = struct + module FI = FloatDomain.FloatInterval + module IT = IntDomain.IntDomTuple + + let fi_zero = FI.of_const 0. + let fi_one = FI.of_const 1. + let fi_neg_one = FI.of_const (-.1.) + let itb_true = IT.of_interval IBool (Big_int_Z.big_int_of_int 1, Big_int_Z.big_int_of_int 1) + let itb_false = IT.of_interval IBool (Big_int_Z.big_int_of_int 0, Big_int_Z.big_int_of_int 0) + let itb_unknown = IT.of_interval IBool (Big_int_Z.big_int_of_int 0, Big_int_Z.big_int_of_int 1) + + let assert_equal v1 v2 = + assert_equal ~cmp:FI.equal ~printer:FI.show v1 v2 + + let itb_xor v1 v2 = (**returns true, if both IntDomainTuple bool results are either unknown or different *) + ((IT.equal v1 itb_unknown) && (IT.equal v2 itb_unknown)) || ((IT.equal v1 itb_true) && (IT.equal v2 itb_false)) || ((IT.equal v1 itb_false) && (IT.equal v2 itb_true)) + + (**interval tests *) + let test_FI_nan _ = + assert_equal (FI.top ()) (FI.of_const Float.nan) + + let test_FI_add1 _ = + assert_equal fi_one (FI.add fi_zero fi_one) + + let test_FI_add2 _ = + assert_equal fi_zero (FI.add fi_one fi_neg_one) + + (**interval tests using QCheck arbitraries *) + let test_FI_not_bot = + QCheck.Test.make ~name:"test_FI_not_bot" (FI.arbitrary ()) (fun arg -> + not (FI.is_bot arg)) + + let test_FI_of_const_not_bot = + QCheck.Test.make ~name:"test_FI_of_const_not_bot" QCheck.float (fun arg -> + not (FI.is_bot (FI.of_const arg))) - let exampleTest2 _ = - assert_equal 0 0; - assert_equal 1 1 + let test_FI_div_zero_result_top = + QCheck.Test.make ~name:"test_FI_div_zero_result_top" (FI.arbitrary ()) (fun arg -> + FI.is_top (FI.div arg (FI.of_const 0.))) + + let test_FI_accurate_neg = + QCheck.Test.make ~name:"test_FI_accurate_neg" QCheck.float (fun arg -> + FI.equal (FI.of_const (-.arg)) (FI.neg (FI.of_const arg))) + + let test_FI_lt_xor_ge = + QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> + itb_xor (FI.lt arg1 arg2) (FI.ge arg1 arg2)) + + let test_FI_gt_xor_le = + QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> + itb_xor (FI.gt arg1 arg2) (FI.le arg1 arg2)) + + let test_FI_eq_xor_ne = + QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> + itb_xor (FI.eq arg1 arg2) (FI.ne arg1 arg2)) + + let test_FI_add = + QCheck.Test.make ~name:"test_FI_add" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> + let result = FI.add (FI.of_const arg1) (FI.of_const arg2) in + (FI.leq (FI.of_const (add Up arg1 arg2)) result) && (FI.leq (FI.of_const (add Down arg1 arg2)) result)) + + let test_FI_sub = + QCheck.Test.make ~name:"test_FI_sub" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> + let result = FI.sub (FI.of_const arg1) (FI.of_const arg2) in + (FI.leq (FI.of_const (sub Up arg1 arg2)) result) && (FI.leq (FI.of_const (sub Down arg1 arg2)) result)) + + let test_FI_mul = + QCheck.Test.make ~name:"test_FI_mul" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> + let result = FI.mul (FI.of_const arg1) (FI.of_const arg2) in + (FI.leq (FI.of_const (mul Up arg1 arg2)) result) && (FI.leq (FI.of_const (mul Down arg1 arg2)) result)) + + let test_FI_div = + QCheck.Test.make ~name:"test_FI_div" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> + let result = FI.div (FI.of_const arg1) (FI.of_const arg2) in + (FI.leq (FI.of_const (div Up arg1 arg2)) result) && (FI.leq (FI.of_const (div Down arg1 arg2)) result)) let test () = [ - "exampleTestModule2" >:: exampleTest2; + "test_FI_nan" >:: test_FI_nan; + "test_FI_add1" >:: test_FI_add1; + "test_FI_add2" >:: test_FI_add2; ] + + let test_qcheck () = QCheck_ounit.to_ounit2_test_list [ + test_FI_not_bot; + test_FI_of_const_not_bot; + test_FI_div_zero_result_top; + test_FI_accurate_neg; + test_FI_lt_xor_ge; + test_FI_gt_xor_le; + test_FI_eq_xor_ne; + test_FI_add; + test_FI_sub; + test_FI_mul; + test_FI_div; + ] end let test () = "floatDomainTest" >::: - [ "Example Test 1" >:: exampleTest1; - "Example Test 2" >::: ExampleTestModule2.test() + [ + "float_interval" >::: FloatInterval.test (); + "float_interval_qcheck" >::: FloatInterval.test_qcheck (); ] \ No newline at end of file diff --git a/unittest/mainTest.ml b/unittest/mainTest.ml index 1ac06997d7..3dfd9cedd7 100644 --- a/unittest/mainTest.ml +++ b/unittest/mainTest.ml @@ -11,6 +11,4 @@ let all_tests = ("" >::: "domaintest" >::: QCheck_ounit.to_ounit2_test_list Maindomaintest.all_testsuite ]) -let all_tests_debug = ("" >::: [FloatDomainTest.test ()]) (* TODO: TO BE REMOVED only for personal Debugging *) - -let () = run_test_tt_main all_tests_debug (* TODO: change back to all_tests *) +let () = run_test_tt_main all_tests \ No newline at end of file From 994e511b3310b605ca9bfc1b6f6b0896c7b0a21f Mon Sep 17 00:00:00 2001 From: FelixKrayer Date: Mon, 23 May 2022 09:45:14 +0200 Subject: [PATCH 10/54] some fixes and suggested changes --- src/cdomains/floatDomain.ml | 199 +++++++++++++-------------- src/util/precisionUtil.ml | 10 +- unittest/cdomains/floatDomainTest.ml | 48 +++---- unittest/mainTest.ml | 24 ++-- 4 files changed, 137 insertions(+), 144 deletions(-) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 85d785ca21..8640a918c2 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -133,115 +133,106 @@ module FloatInterval = struct let neg = Option.map (fun (low, high) -> (-.high, -.low)) - let add op1 op2 = - match op1, op2 with - | Some (l1, h1), Some (l2, h2) -> Some (add Down l1 l2, add Up h1 h2) + (** evaluation of the binary operations *) + let eval_binop eval_operation op1 op2 = + match (op1, op2) with + | Some v1, Some v2 -> norm @@ eval_operation v1 v2 | _ -> None - let sub op1 op2 = - match op1, op2 with - | Some (l1, h1), Some (l2, h2) -> norm @@ Some (sub Down l1 l2, sub Up h1 h2) - | _ -> None + let eval_int_binop eval_operation op1 op2 = + let a, b = + match (op1, op2) with + | Some v1, Some v2 -> eval_operation v1 v2 + | _ -> (0, 1) + in + IntDomain.IntDomTuple.of_interval IBool + (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) - let mul op1 op2 = - match op1, op2 with - | Some (l1, h1), Some (l2, h2) -> - let mul1u = mul Up l1 l2 in - let mul2u = mul Up l1 h2 in - let mul3u = mul Up h1 l2 in - let mul4u = mul Up h1 h2 in - let mul1d = mul Down l1 l2 in - let mul2d = mul Down l1 h2 in - let mul3d = mul Down h1 l2 in - let mul4d = mul Down h1 h2 in - let high = max (max (max mul1u mul2u) mul3u) mul4u in - let low = min (min (min mul1d mul2d) mul3d) mul4d in - norm @@ Some (low, high) - | _ -> None + let eval_add (l1, h1) (l2, h2) = + Some (add Down l1 l2, add Up h1 h2) - let div op1 op2 = - match op1, op2 with - | Some (l1, h1), Some (l2, h2) -> - if l2 <= 0. && h2 >= 0. then None - else - let div1u = div Up l1 l2 in - let div2u = div Up l1 h2 in - let div3u = div Up h1 l2 in - let div4u = div Up h1 h2 in - let div1d = div Down l1 l2 in - let div2d = div Down l1 h2 in - let div3d = div Down h1 l2 in - let div4d = div Down h1 h2 in - let high = max (max (max div1u div2u) div3u) div4u in - let low = min (min (min div1d div2d) div3d) div4d in - norm @@ Some (low, high) - | _ -> None + let eval_sub (l1, h1) (l2, h2) = + Some (sub Down l1 l2, sub Up h1 h2) + + let eval_mul (l1, h1) (l2, h2) = + let mul1u = mul Up l1 l2 in + let mul2u = mul Up l1 h2 in + let mul3u = mul Up h1 l2 in + let mul4u = mul Up h1 h2 in + let mul1d = mul Down l1 l2 in + let mul2d = mul Down l1 h2 in + let mul3d = mul Down h1 l2 in + let mul4d = mul Down h1 h2 in + let high = max (max (max mul1u mul2u) mul3u) mul4u in + let low = min (min (min mul1d mul2d) mul3d) mul4d in + Some (low, high) + + let eval_div (l1, h1) (l2, h2) = + if l2 <= 0. && h2 >= 0. then None + else + let div1u = div Up l1 l2 in + let div2u = div Up l1 h2 in + let div3u = div Up h1 l2 in + let div4u = div Up h1 h2 in + let div1d = div Down l1 l2 in + let div2d = div Down l1 h2 in + let div3d = div Down h1 l2 in + let div4d = div Down h1 h2 in + let high = max (max (max div1u div2u) div3u) div4u in + let low = min (min (min div1d div2d) div3d) div4d in + Some (low, high) + + + let eval_lt (l1, h1) (l2, h2) = + if h1 < l2 then (1, 1) + else if l1 >= h2 then (0, 0) + else (0, 1) + + let eval_gt (l1, h1) (l2, h2) = + if l1 > h2 then (1, 1) + else if h1 <= l2 then (0, 0) + else (0, 1) + + let eval_le (l1, h1) (l2, h2) = + if h1 <= l2 then (1, 1) + else if l1 > h2 then (0, 0) + else (0, 1) + + let eval_ge (l1, h1) (l2, h2) = + if l1 >= h2 then (1, 1) + else if h1 < l2 then (0, 0) + else (0, 1) + + let eval_eq (l1, h1) (l2, h2) = + if h1 < l2 || h2 < l1 then (0, 0) + else if h1 = l1 && h2 = l2 && l1 = l2 then (1, 1) + else (0, 1) + + let eval_ne (l1, h1) (l2, h2) = + if h1 < l2 || h2 < l1 then (1, 1) + else if h1 = l1 && h2 = l2 && l1 = l2 then (0, 0) + else (0, 1) + + let add = eval_binop eval_add + + let sub = eval_binop eval_sub + + let mul = eval_binop eval_mul + + let div = eval_binop eval_div + + let lt = eval_int_binop eval_lt + + let gt = eval_int_binop eval_gt + + let le = eval_int_binop eval_le + + let ge = eval_int_binop eval_ge + + let eq = eval_int_binop eval_eq + + let ne = eval_int_binop eval_ne - let lt op1 op2 = - let (a, b) = - match op1, op2 with - | Some (l1, h1), Some (l2, h2) -> - if h1 < l2 then (1, 1) - else if l1 >= h2 then (0, 0) - else (0, 1) - | _ -> (0, 1) - in - IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) - - let gt op1 op2 = - let (a, b) = - match op1, op2 with - | Some (l1, h1), Some (l2, h2) -> - if l1 > h2 then (1, 1) - else if h1 <= l2 then (0, 0) - else (0, 1) - | _ -> (0, 1) - in - IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) - - let le op1 op2 = - let (a, b) = - match op1, op2 with - | Some (l1, h1), Some (l2, h2) -> - if h1 <= l2 then (1, 1) - else if l1 > h2 then (0, 0) - else (0, 1) - | _ -> (0, 1) - in - IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) - - let ge op1 op2 = - let (a, b) = - match op1, op2 with - | Some (l1, h1), Some (l2, h2) -> - if l1 >= h2 then (1, 1) - else if h1 < l2 then (0, 0) - else (0, 1) - | _ -> (0, 1) - in - IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) - - let eq op1 op2 = - let (a, b) = - match op1, op2 with - | Some (l1, h1), Some (l2, h2) -> - if h1 < l2 || h2 < l1 then (0, 0) - else if h1 = l1 && h2 = l2 && l1 = l2 then (1, 1) - else (0, 1) - | _ -> (0, 1) - in - IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) - - let ne op1 op2 = - let (a, b) = - match op1, op2 with - | Some (l1, h1), Some (l2, h2) -> - if h1 < l2 || h2 < l1 then (1, 1) - else if h1 = l1 && h2 = l2 && l1 = l2 then (0, 0) - else (0, 1) - | _ -> (0, 1) - in - IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) end module FloatDomTupleImpl = struct diff --git a/src/util/precisionUtil.ml b/src/util/precisionUtil.ml index 9d45110dfe..84250e9009 100644 --- a/src/util/precisionUtil.ml +++ b/src/util/precisionUtil.ml @@ -15,17 +15,17 @@ let int_precision_from_fundec (fd: Cil.fundec): int_precision = (ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.enums" ~removeAttr:"no-enums" ~keepAttr:"enums" fd), (ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.congruence" ~removeAttr:"no-congruence" ~keepAttr:"congruence" fd)) - let float_precision_from_fundec (fd: Cil.fundec): float_precision = +let float_precision_from_fundec (fd: Cil.fundec): float_precision = ((ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.float.interval" ~removeAttr:"no-float-interval" ~keepAttr:"float-interval" fd)) let int_precision_from_node (): int_precision = match !MyCFG.current_node with | Some n -> int_precision_from_fundec (Node.find_fundec n) | _ -> max_int_precision (* In case a Node is None we have to handle Globals, i.e. we activate all IntDomains (TODO: verify this assumption) *) - let float_precision_from_node (): float_precision = - match !MyCFG.current_node with - | Some n -> float_precision_from_fundec (Node.find_fundec n) - | _ -> max_float_precision +let float_precision_from_node (): float_precision = + match !MyCFG.current_node with + | Some n -> float_precision_from_fundec (Node.find_fundec n) + | _ -> max_float_precision let int_precision_from_node_or_config (): int_precision = if GobConfig.get_bool "annotation.int.enabled" then diff --git a/unittest/cdomains/floatDomainTest.ml b/unittest/cdomains/floatDomainTest.ml index 9d12a2bce8..06da43a139 100644 --- a/unittest/cdomains/floatDomainTest.ml +++ b/unittest/cdomains/floatDomainTest.ml @@ -1,89 +1,91 @@ open OUnit2 open Round - + module FloatInterval = struct module FI = FloatDomain.FloatInterval module IT = IntDomain.IntDomTuple - + let fi_zero = FI.of_const 0. let fi_one = FI.of_const 1. let fi_neg_one = FI.of_const (-.1.) - let itb_true = IT.of_interval IBool (Big_int_Z.big_int_of_int 1, Big_int_Z.big_int_of_int 1) - let itb_false = IT.of_interval IBool (Big_int_Z.big_int_of_int 0, Big_int_Z.big_int_of_int 0) + let itb_true = IT.of_int IBool (Big_int_Z.big_int_of_int 1) + let itb_false = IT.of_int IBool (Big_int_Z.big_int_of_int 0) let itb_unknown = IT.of_interval IBool (Big_int_Z.big_int_of_int 0, Big_int_Z.big_int_of_int 1) - + let assert_equal v1 v2 = assert_equal ~cmp:FI.equal ~printer:FI.show v1 v2 - + let itb_xor v1 v2 = (**returns true, if both IntDomainTuple bool results are either unknown or different *) ((IT.equal v1 itb_unknown) && (IT.equal v2 itb_unknown)) || ((IT.equal v1 itb_true) && (IT.equal v2 itb_false)) || ((IT.equal v1 itb_false) && (IT.equal v2 itb_true)) - + (**interval tests *) let test_FI_nan _ = assert_equal (FI.top ()) (FI.of_const Float.nan) - + let test_FI_add1 _ = assert_equal fi_one (FI.add fi_zero fi_one) - + let test_FI_add2 _ = assert_equal fi_zero (FI.add fi_one fi_neg_one) - + + (**TODO: add tests for specific edge cases (eg. overflow to infinity when dbl.max + 1) *) + (**interval tests using QCheck arbitraries *) let test_FI_not_bot = QCheck.Test.make ~name:"test_FI_not_bot" (FI.arbitrary ()) (fun arg -> not (FI.is_bot arg)) - + let test_FI_of_const_not_bot = QCheck.Test.make ~name:"test_FI_of_const_not_bot" QCheck.float (fun arg -> not (FI.is_bot (FI.of_const arg))) - + let test_FI_div_zero_result_top = QCheck.Test.make ~name:"test_FI_div_zero_result_top" (FI.arbitrary ()) (fun arg -> FI.is_top (FI.div arg (FI.of_const 0.))) - + let test_FI_accurate_neg = QCheck.Test.make ~name:"test_FI_accurate_neg" QCheck.float (fun arg -> FI.equal (FI.of_const (-.arg)) (FI.neg (FI.of_const arg))) - + let test_FI_lt_xor_ge = QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> itb_xor (FI.lt arg1 arg2) (FI.ge arg1 arg2)) - + let test_FI_gt_xor_le = QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> itb_xor (FI.gt arg1 arg2) (FI.le arg1 arg2)) - + let test_FI_eq_xor_ne = QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> itb_xor (FI.eq arg1 arg2) (FI.ne arg1 arg2)) - + let test_FI_add = QCheck.Test.make ~name:"test_FI_add" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.add (FI.of_const arg1) (FI.of_const arg2) in (FI.leq (FI.of_const (add Up arg1 arg2)) result) && (FI.leq (FI.of_const (add Down arg1 arg2)) result)) - + let test_FI_sub = QCheck.Test.make ~name:"test_FI_sub" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.sub (FI.of_const arg1) (FI.of_const arg2) in (FI.leq (FI.of_const (sub Up arg1 arg2)) result) && (FI.leq (FI.of_const (sub Down arg1 arg2)) result)) - + let test_FI_mul = QCheck.Test.make ~name:"test_FI_mul" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.mul (FI.of_const arg1) (FI.of_const arg2) in (FI.leq (FI.of_const (mul Up arg1 arg2)) result) && (FI.leq (FI.of_const (mul Down arg1 arg2)) result)) - + let test_FI_div = QCheck.Test.make ~name:"test_FI_div" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.div (FI.of_const arg1) (FI.of_const arg2) in (FI.leq (FI.of_const (div Up arg1 arg2)) result) && (FI.leq (FI.of_const (div Down arg1 arg2)) result)) - + let test () = [ "test_FI_nan" >:: test_FI_nan; "test_FI_add1" >:: test_FI_add1; "test_FI_add2" >:: test_FI_add2; ] - + let test_qcheck () = QCheck_ounit.to_ounit2_test_list [ test_FI_not_bot; test_FI_of_const_not_bot; @@ -98,7 +100,7 @@ struct test_FI_div; ] end - + let test () = "floatDomainTest" >::: [ "float_interval" >::: FloatInterval.test (); diff --git a/unittest/mainTest.ml b/unittest/mainTest.ml index 3dfd9cedd7..04ec2d3553 100644 --- a/unittest/mainTest.ml +++ b/unittest/mainTest.ml @@ -1,14 +1,14 @@ -open OUnit2 +open OUnit2 -let all_tests = ("" >::: - [ IntDomainTest.test (); - FloatDomainTest.test (); - MapDomainTest.test (); - SolverTest.test (); - LvalTest.test (); - CompilationDatabaseTest.tests; - (* etc *) - "domaintest" >::: QCheck_ounit.to_ounit2_test_list Maindomaintest.all_testsuite - ]) +let all_tests = ("" >::: + [ IntDomainTest.test (); + FloatDomainTest.test (); + MapDomainTest.test (); + SolverTest.test (); + LvalTest.test (); + CompilationDatabaseTest.tests; + (* etc *) + "domaintest" >::: QCheck_ounit.to_ounit2_test_list Maindomaintest.all_testsuite + ]) -let () = run_test_tt_main all_tests \ No newline at end of file +let () = run_test_tt_main all_tests From 8c7c7f46c2c0b5fb65d95636f38f897b9d5bcff2 Mon Sep 17 00:00:00 2001 From: Dominik Berger Date: Mon, 30 May 2022 17:28:28 +0200 Subject: [PATCH 11/54] Add basic warnings for float domain * add some implementation for the "norm" method in FloatDomain, however at the moment incorrect Now, the norm method is called at eval_binop and successfully fails, which currently is wanted like this, just to test that it works. We need to change it now s.t. it prints a warning instead of an error. * Rework norm slightly s.t. it uses set_overflow_flag now, which however is still incorrect * Add FloatMessage message category in messageCategory.ml Also add it in options.schema.json * Add a todo note for later in sarifRules.ml * Add a regression test for float that expects a warning for division by zeron / infinity * Clean up floatDomain.ml slightly: Remove TODOs/FIXMEs and inline message warning * Fix regression test naming and merge them together * Remove FloatMessage module in messageCategory.ml, as it's not needed anymore * Remove todo message * Remove todo message * Remove todo comments and/or clean up some stuff * Clean up float domain etc. with fixes from PR - Better warning message - use is_top function instead of checking for None in the domain - revert unrelated change - make 'warn.float' option not a default option - make regression test as minimal as possible * In floatDomain.ml, rearrange of_const function s.t. it finds the norm function * Add float warning parameter to 03-infinity_or_nan.c regression test --- src/analyses/base.ml | 2 +- src/cdomains/floatDomain.ml | 76 ++++++++++--------- src/util/messageCategory.ml | 8 +- src/util/options.schema.json | 6 ++ src/util/sarifRules.ml | 1 + .../regression/56-floats/03-infinity_or_nan.c | 10 +++ 6 files changed, 64 insertions(+), 39 deletions(-) create mode 100644 tests/regression/56-floats/03-infinity_or_nan.c diff --git a/src/analyses/base.ml b/src/analyses/base.ml index cb5b03e6d9..6f1d09fd40 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -461,7 +461,7 @@ struct | `List e -> reachable_from_value ask gs st (`Address (ValueDomain.Lists.entry_rand e)) t description | `Struct s -> ValueDomain.Structs.fold (fun k v acc -> AD.join (reachable_from_value ask gs st v t description) acc) s empty | `Int _ -> empty - | `Float _ -> failwith "todo" + | `Float _ -> empty | `Thread _ -> empty (* thread IDs are abstract and nothing known can be reached from them *) (* Get the list of addresses accessable immediately from a given address, thus diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 8640a918c2..0dfc50cd31 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -37,29 +37,9 @@ module type FloatDomainBase = sig val of_const : float -> t end - module FloatInterval = struct type t = (float * float) option [@@deriving eq, ord, to_yojson] - let norm v = - match v with - | Some (low, high) -> - if Float.is_finite low && Float.is_finite high then - if low > high then failwith "invalid Interval" - else v - else None - | _ -> None - - (**just for norming the arbitraries, so correct intervals get created, but no failwith if low > high*) - let norm_arb v = - match v with - | Some (f1, f2) -> - if Float.is_finite f1 && Float.is_finite f2 - then Some(min f1 f2, max f1 f2) - else None - | _ -> None - - let of_const f = norm @@ Some (f, f) let hash = Hashtbl.hash @@ -77,10 +57,48 @@ module FloatInterval = struct let invariant _ (x : t) = failwith "todo invariant" let tag (x : t) = failwith "todo tag" (**Quote printable.ml line 24: Unique ID, given by HConsed, for context identification in witness *) + (** If [leq x y = false], then [pretty_diff () (x, y)] should explain why. *) + + let pretty_diff () (x, y) = + Pretty.dprintf "%a instead of %a" pretty x pretty y + + let bot () = failwith "no bot exists" + + let is_bot _ = false + + let top () = None + + let is_top = Option.is_none + + let neg = Option.map (fun (low, high) -> (-.high, -.low)) + + let norm v = + let normed = match v with + | Some (low, high) -> + if Float.is_finite low && Float.is_finite high then + if low > high then failwith "invalid Interval" + else v + else None + | _ -> None + in if is_top normed then + Messages.warn ~category:Messages.Category.FloatMessage ~tags:[CWE 189; CWE 739] + "Float could be +/-infinity or Nan"; + normed + + (**just for norming the arbitraries, so correct intervals get created, but no failwith if low > high*) + let norm_arb v = + match v with + | Some (f1, f2) -> + if Float.is_finite f1 && Float.is_finite f2 + then Some(min f1 f2, max f1 f2) + else None + | _ -> None (**for QCheck: should describe how to generate random values and shrink possilbe counter examples *) let arbitrary () = QCheck.map norm_arb (QCheck.option (QCheck.pair QCheck.float QCheck.float)) + let of_const f = norm @@ Some (f, f) + let relift x = x let leq v1 v2 = @@ -119,24 +137,10 @@ module FloatInterval = struct | None, _ -> v2 | _, _ -> v1 - (** If [leq x y = false], then [pretty_diff () (x, y)] should explain why. *) - let pretty_diff () (x, y) = - Pretty.dprintf "%a instead of %a" pretty x pretty y - - let bot () = failwith "no bot exists" - - let is_bot _ = false - - let top () = None - - let is_top = Option.is_none - - let neg = Option.map (fun (low, high) -> (-.high, -.low)) - (** evaluation of the binary operations *) let eval_binop eval_operation op1 op2 = - match (op1, op2) with - | Some v1, Some v2 -> norm @@ eval_operation v1 v2 + norm @@ match (op1, op2) with + | Some v1, Some v2 -> eval_operation v1 v2 | _ -> None let eval_int_binop eval_operation op1 op2 = diff --git a/src/util/messageCategory.ml b/src/util/messageCategory.ml index 6ec026fa83..cc9d6258d3 100644 --- a/src/util/messageCategory.ml +++ b/src/util/messageCategory.ml @@ -27,6 +27,7 @@ type category = | Assert | Behavior of behavior | Integer of integer + | FloatMessage | Race | Deadlock | Cast of cast @@ -161,6 +162,7 @@ let should_warn e = | Assert -> "assert" | Behavior _ -> "behavior" | Integer _ -> "integer" + | FloatMessage -> "float" | Race -> "race" | Deadlock -> "deadlock" | Cast _ -> "cast" @@ -176,6 +178,7 @@ let path_show e = | Assert -> ["Assert"] | Behavior x -> "Behavior" :: Behavior.path_show x | Integer x -> "Integer" :: Integer.path_show x + | FloatMessage -> ["Float"] | Race -> ["Race"] | Deadlock -> ["Deadlock"] | Cast x -> "Cast" :: Cast.path_show x @@ -210,9 +213,10 @@ let categoryName = function | Imprecise -> "Imprecise" | Behavior x -> behaviorName x - | Integer x -> match x with + | Integer x -> (match x with | Overflow -> "Overflow"; - | DivByZero -> "DivByZero" + | DivByZero -> "DivByZero") + | FloatMessage -> "Float" let from_string_list (s: string list) = diff --git a/src/util/options.schema.json b/src/util/options.schema.json index dd39e931ea..2a48f7fc1d 100644 --- a/src/util/options.schema.json +++ b/src/util/options.schema.json @@ -1697,6 +1697,12 @@ "type": "boolean", "default": true }, + "float": { + "title": "warn.float", + "description": "float warnings", + "type": "boolean", + "default": false + }, "cast": { "title": "warn.cast", "description": "Cast (Type_mismatch(bug) warnings", diff --git a/src/util/sarifRules.ml b/src/util/sarifRules.ml index dff04893c3..f0c8da4eb5 100644 --- a/src/util/sarifRules.ml +++ b/src/util/sarifRules.ml @@ -57,6 +57,7 @@ let rules = [ helpUri="https://goblint.in.tum.de/home"; longDescription=""; }; + (* TODO: We'll want to add something like this "Integer Overflow" here also for our float! *) { name="Overflow"; ruleId="GO0006"; diff --git a/tests/regression/56-floats/03-infinity_or_nan.c b/tests/regression/56-floats/03-infinity_or_nan.c new file mode 100644 index 0000000000..ea8e846de9 --- /dev/null +++ b/tests/regression/56-floats/03-infinity_or_nan.c @@ -0,0 +1,10 @@ +// PARAM: --enable ana.float.interval --enable warn.float +#include + +void main() +{ + double data; + + double result1 = data + 1.0; // WARN: Could be +/-infinity or Nan + double result2 = data / 0.; // WARN: Could be +/-infinity or Nan +} From b0c6bb1af90323e1c08b57029bd57392b824e882 Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Fri, 13 May 2022 08:15:36 +0200 Subject: [PATCH 12/54] Provide cast support between int types and double --- src/analyses/base.ml | 4 +- src/cdomains/floatDomain.ml | 38 ++++++++++++++++- src/cdomains/floatDomain.mli | 3 +- src/cdomains/valueDomain.ml | 21 ++++++---- tests/regression/56-floats/04-casts.c | 59 +++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 11 deletions(-) create mode 100644 tests/regression/56-floats/04-casts.c diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 6f1d09fd40..6095a5a680 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -137,7 +137,7 @@ struct (* Evaluating Cil's unary operators. *) let evalunop op typ = function | `Int v1 -> `Int (ID.cast_to (Cilfacade.get_ikind typ) (unop_ID op v1)) - | `Float v -> `Float (unop_FD op v) (* TODO(Practical2022): Do we require a type check? *) + | `Float v -> `Float (unop_FD op v) | `Address a when op = LNot -> if AD.is_null a then `Int (ID.of_bool (Cilfacade.get_ikind typ) true) @@ -727,7 +727,7 @@ struct | Const (CInt (num,ikind,str)) -> (match str with Some x -> M.tracel "casto" "CInt (%s, %a, %s)\n" (Cilint.string_of_cilint num) d_ikind ikind x | None -> ()); `Int (ID.cast_to ikind (IntDomain.of_const (num,ikind,str))) - | Const (CReal (num, _, _)) -> `Float (FD.of_const num) (* TODO(Practical(2022): use string representation instead *) + | Const (CReal (num, FDouble, _)) -> `Float (FD.of_const num) (* TODO(Practical(2022): use string representation instead, extend to other floating point types *) (* String literals *) | Const (CStr (x,_)) -> `Address (AD.from_string x) (* normal 8-bit strings, type: char* *) | Const (CWStr (xs,_) as c) -> (* wide character strings, type: wchar_t* *) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 0dfc50cd31..fc141a8395 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -36,11 +36,40 @@ module type FloatDomainBase = sig include FloatArith with type t := t val of_const : float -> t + val of_int: IntDomain.IntDomTuple.t -> t + val cast_to: Cil.ikind -> t -> IntDomain.IntDomTuple.t end module FloatInterval = struct type t = (float * float) option [@@deriving eq, ord, to_yojson] + let big_int_of_float f = + let x, n = Float.frexp f in + let shift = min 52 n in + let x' = x *. Float.pow 2. (Float.of_int shift) in + Big_int_Z.mult_big_int + (Big_int_Z.big_int_of_int64 (Int64.of_float x')) + (Big_int_Z.power_int_positive_int 2 (n - shift)) + + let cast_to ik = function + | None -> IntDomain.IntDomTuple.top_of ik + | Some (l, h) -> + (* as converting from float to integer is (exactly) defined as leaving out the fractional part, + (value is truncated towrad zero) we do not require specific rounding here *) + IntDomain.IntDomTuple.of_interval ik (big_int_of_float l, big_int_of_float h) + + let of_int x = match IntDomain.IntDomTuple.minimal x, IntDomain.IntDomTuple.maximal x with + | Some l, Some h when l >= big_int_of_float (-. Float.max_float) && h <= big_int_of_float Float.max_float -> + let l' = Big_int_Z.float_of_big_int l in + let l'' = if big_int_of_float (l') <= l then l' else Float.pred l' in + let h' = Big_int_Z.float_of_big_int h in + let h'' = if big_int_of_float (h') >= h then h' else Float.succ h' in + if l'' = Float.neg_infinity || h'' = Float.infinity then + None + else + Some (l'', h'') + | _, _ -> None + let hash = Hashtbl.hash let show = function @@ -83,7 +112,7 @@ module FloatInterval = struct in if is_top normed then Messages.warn ~category:Messages.Category.FloatMessage ~tags:[CWE 189; CWE 739] "Float could be +/-infinity or Nan"; - normed + normed (**just for norming the arbitraries, so correct intervals get created, but no failwith if low > high*) let norm_arb v = @@ -308,6 +337,13 @@ module FloatDomTupleImpl = struct for_all % mapp { fp= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.is_top); } + let of_int = + create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.of_int); } + + let cast_to ik x = + match x with + | Some f -> F1.cast_to ik f + | None -> IntDomain.IntDomTuple.top_of ik let leq = for_all diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index bafa52325f..186ddd6338 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -53,10 +53,11 @@ end module type FloatDomainBase = sig include Lattice.S - include FloatArith with type t := t val of_const : float -> t + val of_int : IntDomain.IntDomTuple.t -> t + val cast_to : Cil.ikind -> t -> IntDomain.IntDomTuple.t end module FloatDomTupleImpl : sig diff --git a/src/cdomains/valueDomain.ml b/src/cdomains/valueDomain.ml index 2837d4f5e6..ca84edb488 100644 --- a/src/cdomains/valueDomain.ml +++ b/src/cdomains/valueDomain.ml @@ -148,7 +148,8 @@ struct match t with | t when is_mutex_type t -> `Top | TInt (ik,_) -> `Int (ID.top_of ik) - | TFloat (FDouble, _) -> `Float (FD.top ()) (* TODO(Practical2022): extend to other floating point types *) + | TFloat (FDouble, _) -> `Float (FD.top ()) + | TFloat (fk, _) -> `Top (* TODO(Practical2022): extend to other floating point types *) | TPtr _ -> `Address AD.top_ptr | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> init_value fd.ftype) ci) | TComp ({cstruct=false; _},_) -> `Union (Unions.top ()) @@ -164,7 +165,8 @@ struct let rec top_value (t: typ): t = match t with | TInt (ik,_) -> `Int (ID.(cast_to ik (top_of ik))) - | TFloat (FDouble, _) -> `Float (FD.top ()) (* TODO(Practical2022): extend to other floating point types *) + | TFloat (FDouble, _) -> `Float (FD.top ()) + | TFloat (fk, _) -> `Top (* TODO(Practical2022): extend to other floating point types *) | TPtr _ -> `Address AD.top_ptr | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> top_value fd.ftype) ci) | TComp ({cstruct=false; _},_) -> `Union (Unions.top ()) @@ -193,7 +195,7 @@ struct let rec zero_init_value (t:typ): t = match t with | TInt (ikind, _) -> `Int (ID.of_int ikind BI.zero) - | TFloat (FDouble, _) -> failwith "todo" + | TFloat (FDouble, _) -> `Float (FD.of_const 0.0) | TPtr _ -> `Address AD.null_ptr | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> zero_init_value fd.ftype) ci) | TComp ({cstruct=false; _} as ci,_) -> @@ -371,10 +373,15 @@ struct let log_top (_,l,_,_) = Messages.tracel "cast" "log_top at %d: %a to %a is top!\n" l pretty v d_type t in let t = unrollType t in let v' = match t with - | TFloat (fk,_) -> log_top __POS__; `Top + | TFloat (FDouble,_) -> + `Float (match v with + |`Int ix -> (FD.of_int ix) + | _ -> FD.top ()) + | TFloat (fk,_) -> log_top __POS__; `Top (* TODO(Practical2022): extend to other floating point types *) | TInt (ik,_) -> `Int (ID.cast_to ?torg ik (match v with | `Int x -> x + | `Float x -> FD.cast_to ik x | `Address x when AD.equal x AD.null_ptr -> ID.of_int (ptr_ikind ()) BI.zero | `Address x when AD.is_not_null x -> ID.of_excl_list (ptr_ikind ()) [BI.zero] (*| `Struct x when Structs.cardinal x > 0 -> @@ -479,7 +486,7 @@ struct | (`Bot, x) -> x | (x, `Bot) -> x | (`Int x, `Int y) -> (try `Int (ID.join x y) with IntDomain.IncompatibleIKinds m -> Messages.warn "%s" m; `Top) - | (`Float x, `Float y) -> `Float (FD.join x y) (* TODO(Practical2022): type check?! *) + | (`Float x, `Float y) -> `Float (FD.join x y) | (`Int x, `Address y) | (`Address y, `Int x) -> `Address (match ID.to_int x with | Some x when BI.equal x BI.zero -> AD.join AD.null_ptr y @@ -514,7 +521,7 @@ struct | (`Bot, x) -> x | (x, `Bot) -> x | (`Int x, `Int y) -> (try `Int (ID.join x y) with IntDomain.IncompatibleIKinds m -> Messages.warn "%s" m; `Top) - | (`Float x, `Float y) -> `Float (FD.join x y) (* TODO(Practical2022): type check?! *) + | (`Float x, `Float y) -> `Float (FD.join x y) | (`Int x, `Address y) | (`Address y, `Int x) -> `Address (match ID.to_int x with | Some x when BI.equal BI.zero x -> AD.join AD.null_ptr y @@ -550,7 +557,7 @@ struct | (`Bot, x) -> x | (x, `Bot) -> x | (`Int x, `Int y) -> (try `Int (ID.widen x y) with IntDomain.IncompatibleIKinds m -> Messages.warn "%s" m; `Top) - | (`Float x, `Float y) -> `Float (FD.widen x y) (* TODO(Practical2022): type check?! *) + | (`Float x, `Float y) -> `Float (FD.widen x y) | (`Int x, `Address y) | (`Address y, `Int x) -> `Address (match ID.to_int x with | Some x when BI.equal BI.zero x -> AD.widen AD.null_ptr y diff --git a/tests/regression/56-floats/04-casts.c b/tests/regression/56-floats/04-casts.c new file mode 100644 index 0000000000..dfd01c57fb --- /dev/null +++ b/tests/regression/56-floats/04-casts.c @@ -0,0 +1,59 @@ +// PARAM: --enable ana.int.interval --enable ana.float.interval +#include + +#define RANGE(val, min, max) \ + if (rnd) \ + { \ + val = min; \ + } \ + else \ + { \ + val = max; \ + } + +int main() +{ + // Casts from double into different variants of ints + assert((int)0.0); // FAIL! + assert((long)0.0); // FAIL! + assert((unsigned)0.0); // FAIL! + + assert((unsigned)1.0); // SUCCESS! + assert((long)2.0); // SUCCESS! + assert((int)3.0); // SUCCESS! + + // Cast from int into double + assert((double)0); // FAIL! + assert((double)0l); // FAIL! + assert((double)0u); // FAIL! + + assert((double)1u); // SUCCESS! + assert((double)2l); // SUCCESS! + assert((double)3); // SUCCESS! + + // cast with ranges + int rnd; + + double value; + int i; + long l; + unsigned u; + + RANGE(i, -5, 5); + value = (double)i; + assert(-5. <= value && value <= 5.); // SUCCESS! + + RANGE(l, 10, 20); + value = l; + assert(10. <= value && value <= 20.); // SUCCESS! + + RANGE(u, 100, 1000); + value = u; + assert(value > 1.); // SUCCESS! + + RANGE(value, -10., 10.); + i = (int)value; + assert(-10 <= i && i <= 10); // SUCCESS! + + return 0; +} From c6b8592b01f8e8ec23ecd43c8d4a9922f234811e Mon Sep 17 00:00:00 2001 From: FelixKrayer Date: Thu, 2 Jun 2022 12:40:22 +0200 Subject: [PATCH 13/54] include Printable.Std for default implementation and fix sub --- src/cdomains/floatDomain.ml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index fc141a8395..d50fe37905 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -40,6 +40,7 @@ module type FloatDomainBase = sig val cast_to: Cil.ikind -> t -> IntDomain.IntDomTuple.t end module FloatInterval = struct + include Printable.Std (* for default invariant, tag and relift *) type t = (float * float) option [@@deriving eq, ord, to_yojson] @@ -83,11 +84,7 @@ module FloatInterval = struct let name () = "FloatInterval" - let invariant _ (x : t) = failwith "todo invariant" - - let tag (x : t) = failwith "todo tag" (**Quote printable.ml line 24: Unique ID, given by HConsed, for context identification in witness *) (** If [leq x y = false], then [pretty_diff () (x, y)] should explain why. *) - let pretty_diff () (x, y) = Pretty.dprintf "%a instead of %a" pretty x pretty y @@ -126,9 +123,7 @@ module FloatInterval = struct (**for QCheck: should describe how to generate random values and shrink possilbe counter examples *) let arbitrary () = QCheck.map norm_arb (QCheck.option (QCheck.pair QCheck.float QCheck.float)) - let of_const f = norm @@ Some (f, f) - - let relift x = x + let of_const f = norm @@ Some (f, f) let leq v1 v2 = match v1, v2 with @@ -185,7 +180,7 @@ module FloatInterval = struct Some (add Down l1 l2, add Up h1 h2) let eval_sub (l1, h1) (l2, h2) = - Some (sub Down l1 l2, sub Up h1 h2) + Some (sub Down l1 h2, sub Up h1 l2) let eval_mul (l1, h1) (l2, h2) = let mul1u = mul Up l1 l2 in From 02d81db0ead87449b11269cdc7fbb70cdd1688e5 Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Thu, 9 Jun 2022 13:38:31 +0200 Subject: [PATCH 14/54] Implementation of invariant for capturing information from branching (#6) --- src/analyses/base.ml | 220 +++++++++++++++------- src/cdomains/floatDomain.ml | 55 +++++- src/cdomains/floatDomain.mli | 12 ++ src/util/cilfacade.ml | 9 + tests/regression/56-floats/05-invariant.c | 99 ++++++++++ 5 files changed, 320 insertions(+), 75 deletions(-) create mode 100644 tests/regression/56-floats/05-invariant.c diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 6095a5a680..cffdf2d6c4 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -128,7 +128,7 @@ struct | Neg -> ID.neg | BNot -> ID.bitnot | LNot -> ID.lognot - + let unop_FD = function | Neg -> FD.neg (* other unary operators are not implemented on float values *) @@ -175,7 +175,7 @@ struct | Mult -> FD.mul | Div -> FD.div | _ -> (fun _ _ -> FD.top ()) - + let int_returning_binop_FD = function | Lt -> FD.lt | Gt -> FD.gt @@ -184,7 +184,7 @@ struct | Eq -> FD.eq | Ne -> FD.ne | _ -> (fun _ _ -> ID.top ()) - + let is_int_returning_binop_FD = function | Lt | Gt | Le | Ge | Eq | Ne -> true | _ -> false @@ -1467,7 +1467,6 @@ struct helper Ne x (null_val (Cilfacade.typeOf exp)) tv | UnOp (LNot,uexp,typ) -> derived_invariant uexp (not tv) | _ -> - (*TODO(Practical2022): Make goblint also understand floats *) if M.tracing then M.tracec "invariant" "Failed! (expression %a not understood)\n\n" d_plainexp exp; None in @@ -1632,14 +1631,69 @@ struct if M.tracing then M.tracel "inv" "Unhandled operator %a\n" d_binop op; a, b in + let inv_bin_float (a, b) fkind c op = + let meet_bin a' b' = FD.meet a a', FD.meet b b' in + let meet_com oi = (* commutative *) + meet_bin (oi c b) (oi c a) in + let meet_non oi oo = (* non-commutative *) + meet_bin (oi c b) (oo a c) in + match op with + | PlusA -> meet_com FD.sub + | Mult -> + (* refine x by information about y, using x * y == c *) + let refine_by x y = (match FD.to_float y with + | None -> x + | Some _ -> FD.meet x (FD.div c y)) + in + (refine_by a b, refine_by b a) + | MinusA -> meet_non FD.add FD.sub + | Div -> + (* If b must be zero, we have must UB *) + meet_bin (FD.mul b c) (FD.div a c) + | Eq | Ne as op -> + let both x = x, x in + let m = FD.meet a b in + let result = FD.ne c (FD.of_const 0.) in + (match op, ID.to_bool(result) with + | Eq, Some true + | Ne, Some false -> both m (* def. equal: if they compare equal, both values must be from the meet *) + | Eq, Some false + | Ne, Some true -> (* def. unequal *) + (* M.debug ~category:Analyzer "Can't use uneqal information about float value in expression \"%a\"." d_plainexp exp; *) + a, b + | _, _ -> a, b + ) + | Lt | Le | Ge | Gt as op -> + (match FD.minimal a, FD.maximal a, FD.minimal b, FD.maximal b with + | Some l1, Some u1, Some l2, Some u2 -> + (match op, ID.to_bool(FD.ne c (FD.of_const 0.)) with + | Le, Some true + | Gt, Some false -> meet_bin (FD.ending u2) (FD.starting l1) + | Ge, Some true + | Lt, Some false -> meet_bin (FD.starting l2) (FD.ending u1) + | Lt, Some true + | Ge, Some false -> meet_bin (FD.ending (Float.pred u2)) (FD.starting (Float.succ l1)) + | Gt, Some true + | Le, Some false -> meet_bin (FD.starting (Float.succ l2)) (FD.ending (Float.pred u1)) + | _, _ -> a, b) + | _ -> a, b) + | op -> + if M.tracing then M.tracel "inv" "Unhandled operator %a\n" d_binop op; + a, b + in let eval e st = eval_rv a gs st e in let eval_bool e st = match eval e st with `Int i -> ID.to_bool i | _ -> None in let set' lval v st = set a gs st (eval_lv a gs st lval) (Cilfacade.typeOfLval lval) v ~invariant:true ~ctx:(Some ctx) in - let rec inv_exp c exp (st:store): store = + let rec inv_exp c_typed exp (st:store): store = (* trying to improve variables in an expression so it is bottom means dead code *) - if ID.is_bot c then raise Deadcode; - match exp with - | UnOp (LNot, e, _) -> + ( + match c_typed with + | `Int c -> if ID.is_bot c then raise Deadcode + | `Float c -> if FD.is_bot c then raise Deadcode + | _ -> if VD.is_bot c_typed then raise Deadcode + ); + match exp, c_typed with + | UnOp (LNot, e, _), `Int c -> let ikind = Cilfacade.get_ikind_exp e in let c' = match ID.to_bool (unop_ID LNot c) with @@ -1650,68 +1704,99 @@ struct | Some false -> ID.of_bool ikind false | _ -> ID.top_of ikind in - inv_exp c' e st - | UnOp ((BNot|Neg) as op, e, _) -> inv_exp (unop_ID op c) e st - | BinOp(op, CastE (t1, c1), CastE (t2, c2), t) when (op = Eq || op = Ne) && typeSig (Cilfacade.typeOf c1) = typeSig (Cilfacade.typeOf c2) && VD.is_safe_cast t1 (Cilfacade.typeOf c1) && VD.is_safe_cast t2 (Cilfacade.typeOf c2) -> - inv_exp c (BinOp (op, c1, c2, t)) st - | BinOp (op, e1, e2, _) as e -> - if M.tracing then M.tracel "inv" "binop %a with %a %a %a == %a\n" d_exp e VD.pretty (eval e1 st) d_binop op VD.pretty (eval e2 st) ID.pretty c; + inv_exp (`Int c') e st + | UnOp (Neg, e, _), `Float c -> inv_exp (`Float (unop_FD Neg c)) e st + | UnOp ((BNot|Neg) as op, e, _), `Int c -> inv_exp (`Int (unop_ID op c)) e st + (* no equivalent for `Float so far, as VD.is_safe_cast fails for all float types anyways *) + | BinOp(op, CastE (t1, c1), CastE (t2, c2), t), `Int c when (op = Eq || op = Ne) && typeSig (Cilfacade.typeOf c1) = typeSig (Cilfacade.typeOf c2) && VD.is_safe_cast t1 (Cilfacade.typeOf c1) && VD.is_safe_cast t2 (Cilfacade.typeOf c2) -> + inv_exp (`Int c) (BinOp (op, c1, c2, t)) st + | (BinOp (op, e1, e2, _) as e, `Float _) + | (BinOp (op, e1, e2, _) as e, `Int _) -> + let invert_binary_op c pretty c_int c_float = + if M.tracing then M.tracel "inv" "binop %a with %a %a %a == %a\n" d_exp e VD.pretty (eval e1 st) d_binop op VD.pretty (eval e2 st) pretty c; (match eval e1 st, eval e2 st with - | `Int a, `Int b -> - let ikind = Cilfacade.get_ikind_exp e1 in (* both operands have the same type (except for Shiftlt, Shiftrt)! *) - let a', b' = inv_bin_int (a, b) ikind c op in - if M.tracing then M.tracel "inv" "binop: %a, a': %a, b': %a\n" d_exp e ID.pretty a' ID.pretty b'; - let st' = inv_exp a' e1 st in - let st'' = inv_exp b' e2 st' in - st'' - (* | `Address a, `Address b -> ... *) - | a1, a2 -> fallback ("binop: got abstract values that are not `Int: " ^ sprint VD.pretty a1 ^ " and " ^ sprint VD.pretty a2) st) - | Lval x -> (* meet x with c *) + | `Int a, `Int b -> + let ikind = Cilfacade.get_ikind_exp e1 in (* both operands have the same type (except for Shiftlt, Shiftrt)! *) + let a', b' = inv_bin_int (a, b) ikind (c_int ikind) op in + if M.tracing then M.tracel "inv" "binop: %a, a': %a, b': %a\n" d_exp e ID.pretty a' ID.pretty b'; + let st' = inv_exp (`Int a') e1 st in + let st'' = inv_exp (`Int b') e2 st' in + st'' + | `Float a, `Float b -> + let fkind = Cilfacade.get_fkind_exp e1 in (* both operands have the same type (except for Shiftlt, Shiftrt)! *) + let a', b' = inv_bin_float (a, b) fkind (c_float fkind) op in + if M.tracing then M.tracel "inv" "binop: %a, a': %a, b': %a\n" d_exp e FD.pretty a' FD.pretty b'; + let st' = inv_exp (`Float a') e1 st in + let st'' = inv_exp (`Float b') e2 st' in + st'' + (* Mixed `Float and `Int cases should never happen, TODO: make this sure ?!*) + | `Int _, `Float _ | `Float _, `Int _ -> failwith "todo - probably ill-typed program"; + (* | `Address a, `Address b -> ... *) + | a1, a2 -> fallback ("binop: got abstract values that are not `Int: " ^ sprint VD.pretty a1 ^ " and " ^ sprint VD.pretty a2) st) + (* use closures to avoid unused casts *) + in (match c_typed with + | `Int c -> invert_binary_op c ID.pretty (fun _ -> c) (fun _ -> FD.of_int c) + | `Float c -> invert_binary_op c FD.pretty (fun ikind -> FD.cast_to ikind c) (fun _ -> c) + | _ -> failwith "unreachable") + | Lval x, `Int _(* meet x with c *) + | Lval x, `Float _ -> (* meet x with c *) + let update_lval c x c' pretty = (match x with + | Var var, o -> + (* For variables, this is done at to the level of entire variables to benefit e.g. from disjunctive struct domains *) + let oldv = get_var a gs st var in + let offs = convert_offset a gs st o in + let newv = VD.update_offset a oldv offs c' (Some exp) x (var.vtype) in + let v = VD.meet oldv newv in + if is_some_bot v then raise Deadcode + else ( + if M.tracing then M.tracel "inv" "improve variable %a from %a to %a (c = %a, c' = %a)\n" d_varinfo var VD.pretty oldv VD.pretty v pretty c VD.pretty c'; + set' (Var var,NoOffset) v st + ) + | Mem _, _ -> + (* For accesses via pointers, not yet *) + let oldv = eval (Lval x) st in + let v = VD.meet oldv c' in + if is_some_bot v then raise Deadcode + else ( + if M.tracing then M.tracel "inv" "improve lval %a from %a to %a (c = %a, c' = %a)\n" d_lval x VD.pretty oldv VD.pretty v pretty c VD.pretty c'; + set' x v st + )) in let t = Cil.unrollType (Cilfacade.typeOfLval x) in (* unroll type to deal with TNamed *) - let c' = match t with - | TPtr _ -> `Address (AD.of_int (module ID) c) - | TInt (ik, _) - | TEnum ({ekind = ik; _}, _) -> `Int (ID.cast_to ik c ) - | _ -> `Int c - in - (match x with - | Var var, o -> - (* For variables, this is done at to the level of entire variables to benefit e.g. from disjunctive struct domains *) - let oldv = get_var a gs st var in - let offs = convert_offset a gs st o in - let newv = VD.update_offset a oldv offs c' (Some exp) x (var.vtype) in - let v = VD.meet oldv newv in - if is_some_bot v then raise Deadcode - else ( - if M.tracing then M.tracel "inv" "improve variable %a from %a to %a (c = %a, c' = %a)\n" d_varinfo var VD.pretty oldv VD.pretty v ID.pretty c VD.pretty c'; - set' (Var var,NoOffset) v st - ) - | Mem _, _ -> - (* For accesses via pointers, not yet *) - let oldv = eval (Lval x) st in - let v = VD.meet oldv c' in - if is_some_bot v then raise Deadcode - else ( - if M.tracing then M.tracel "inv" "improve lval %a from %a to %a (c = %a, c' = %a)\n" d_lval x VD.pretty oldv VD.pretty v ID.pretty c VD.pretty c'; - set' x v st - )) - | Const _ -> st (* nothing to do *) - | CastE ((TInt (ik, _)) as t, e) - | CastE ((TEnum ({ekind = ik; _ }, _)) as t, e) -> (* Can only meet the t part of an Lval in e with c (unless we meet with all overflow possibilities)! Since there is no good way to do this, we only continue if e has no values outside of t. *) + (match c_typed with + | `Int c -> update_lval c x (match t with + | TPtr _ -> `Address (AD.of_int (module ID) c) + | TInt (ik, _) + | TEnum ({ekind = ik; _}, _) -> `Int (ID.cast_to ik c ) + | TFloat (fk, _) -> `Float (FD.of_int c) + | _ -> `Int c) ID.pretty + | `Float c -> update_lval c x (match t with + (* | TPtr _ -> ..., pointer conversion from/to float is not supported *) + | TInt (ik, _) -> `Int (FD.cast_to ik c) + (* this is theoretically possible and should be handled correctly, however i can't imagine an actual piece of c code producing this?! *) + | TEnum ({ekind = ik; _}, _) -> `Int (FD.cast_to ik c) + | TFloat (fk, _) -> `Float c + | _ -> `Float c) FD.pretty + | _ -> failwith "unreachable") + | Const _ , _ -> st (* nothing to do *) + | CastE ((TFloat (FDouble, _)), e), `Float c -> inv_exp (`Float c) e st + | CastE ((TFloat (fk, _)), e), `Float c -> failwith "todo - f2f casts" (* TODO(Practical2022): extend to other floating point types *) + | CastE ((TInt (ik, _)) as t, e), `Int c + | CastE ((TEnum ({ekind = ik; _ }, _)) as t, e), `Int c -> (* Can only meet the t part of an Lval in e with c (unless we meet with all overflow possibilities)! Since there is no good way to do this, we only continue if e has no values outside of t. *) (match eval e st with - | `Int i -> - if ID.leq i (ID.cast_to ik i) then + | `Int i -> + if ID.leq i (ID.cast_to ik i) then match Cilfacade.typeOf e with - | TInt(ik_e, _) - | TEnum ({ekind = ik_e; _ }, _) -> - let c' = ID.cast_to ik_e c in - if M.tracing then M.tracel "inv" "cast: %a from %a to %a: i = %a; cast c = %a to %a = %a\n" d_exp e d_ikind ik_e d_ikind ik ID.pretty i ID.pretty c d_ikind ik_e ID.pretty c'; - inv_exp c' e st - | x -> fallback ("CastE: e did evaluate to `Int, but the type did not match" ^ sprint d_type t) st - else - fallback ("CastE: " ^ sprint d_plainexp e ^ " evaluates to " ^ sprint ID.pretty i ^ " which is bigger than the type it is cast to which is " ^ sprint d_type t) st - | v -> fallback ("CastE: e did not evaluate to `Int, but " ^ sprint VD.pretty v) st) - | e -> fallback (sprint d_plainexp e ^ " not implemented") st + | TInt(ik_e, _) + | TEnum ({ekind = ik_e; _ }, _) -> + let c' = ID.cast_to ik_e c in + if M.tracing then M.tracel "inv" "cast: %a from %a to %a: i = %a; cast c = %a to %a = %a\n" d_exp e d_ikind ik_e d_ikind ik ID.pretty i ID.pretty c d_ikind ik_e ID.pretty c'; + inv_exp (`Int c') e st + | x -> fallback ("CastE: e did evaluate to `Int, but the type did not match" ^ sprint d_type t) st + else + fallback ("CastE: " ^ sprint d_plainexp e ^ " evaluates to " ^ sprint ID.pretty i ^ " which is bigger than the type it is cast to which is " ^ sprint d_type t) st + | `Float f -> inv_exp (`Float (FD.of_int c)) e st + | v -> fallback ("CastE: e did not evaluate to `Int, but " ^ sprint VD.pretty v) st) + | e, _ -> fallback (sprint d_plainexp e ^ " not implemented") st in if eval_bool exp st = Some (not tv) then raise Deadcode (* we already know that the branch is dead *) else @@ -1720,13 +1805,14 @@ struct | _ -> false in let itv = (* int abstraction for tv *) + (* when using floats without explicit cast, we can actually get a non-integer type -> this produces a warning *) let ik = Cilfacade.get_ikind_exp exp in if not tv || is_cmp exp then (* false is 0, but true can be anything that is not 0, except for comparisons which yield 1 *) ID.of_bool ik tv (* this will give 1 for true which is only ok for comparisons *) else ID.of_excl_list ik [BI.zero] (* Lvals, Casts, arithmetic operations etc. should work with true = non_zero *) in - inv_exp itv exp st + inv_exp (`Int itv) exp st let set_savetop ?ctx ?lval_raw ?rval_raw ask (gs:glob_fun) st adr lval_t v : store = if M.tracing then M.tracel "set" "savetop %a %a %a\n" AD.pretty adr d_type lval_t VD.pretty v; diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index d50fe37905..eda7d5fcc2 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -36,13 +36,24 @@ module type FloatDomainBase = sig include FloatArith with type t := t val of_const : float -> t - val of_int: IntDomain.IntDomTuple.t -> t - val cast_to: Cil.ikind -> t -> IntDomain.IntDomTuple.t + val of_interval : float*float -> t + val ending : float -> t + val starting : float -> t + + val to_float : t -> float option + val maximal : t -> float option + val minimal : t -> float option + + val of_int : IntDomain.IntDomTuple.t -> t + val cast_to : Cil.ikind -> t -> IntDomain.IntDomTuple.t end module FloatInterval = struct include Printable.Std (* for default invariant, tag and relift *) type t = (float * float) option [@@deriving eq, ord, to_yojson] + let show = function + | None -> "Float[Top]" + | Some (low, high) -> "Float [" ^ string_of_float low ^ "," ^ string_of_float high ^ "]" let big_int_of_float f = let x, n = Float.frexp f in @@ -71,11 +82,19 @@ module FloatInterval = struct Some (l'', h'') | _, _ -> None - let hash = Hashtbl.hash + let maximal = function + | Some (_, h) -> Some h + | _ -> None - let show = function - | None -> "Float[Top]" - | Some (low, high) -> "Float [" ^ string_of_float low ^ "," ^ string_of_float high ^ "]" + let minimal = function + | Some (l, _) -> Some l + | _ -> None + + let to_float = function + | Some (l, h) when l = h -> Some l + | _ -> None + + let hash = Hashtbl.hash let pretty () x = text (show x) @@ -123,7 +142,10 @@ module FloatInterval = struct (**for QCheck: should describe how to generate random values and shrink possilbe counter examples *) let arbitrary () = QCheck.map norm_arb (QCheck.option (QCheck.pair QCheck.float QCheck.float)) - let of_const f = norm @@ Some (f, f) + let of_interval (l, h) = norm @@ Some (min l h, max l h) + let ending end_value = of_interval (-. max_float, end_value) + let starting start_value = of_interval (start_value, max_float) + let of_const f = of_interval (f, f) let leq v1 v2 = match v1, v2 with @@ -164,7 +186,7 @@ module FloatInterval = struct (** evaluation of the binary operations *) let eval_binop eval_operation op1 op2 = norm @@ match (op1, op2) with - | Some v1, Some v2 -> eval_operation v1 v2 + | Some v1, Some v2 -> eval_operation v1 v2 | _ -> None let eval_int_binop eval_operation op1 op2 = @@ -320,6 +342,23 @@ module FloatDomTupleImpl = struct let of_const = create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.of_const); } + let of_interval = + create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.of_interval); } + let ending = + create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.ending); } + let starting = + create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.starting); } + + let to_float = function + | Some (x) -> F1.to_float x + | _ -> None + + let maximal = function + | Some (x) -> F1.maximal x + | _ -> None + let minimal = function + | Some (x) -> F1.minimal x + | _ -> None let top = create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.top); } diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index 186ddd6338..e9a59c5a84 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -41,6 +41,9 @@ module FloatInterval : sig (**Currently just for FloatDomainTest *) val is_top : t -> bool val of_const : float -> t + val of_interval : float*float -> t + val ending : float -> t + val starting : float -> t val show : t -> string @@ -56,6 +59,15 @@ module type FloatDomainBase = sig include FloatArith with type t := t val of_const : float -> t + val of_interval : float*float -> t + + val ending : float -> t + val starting : float -> t + + val to_float : t -> float option + val maximal : t -> float option + val minimal : t -> float option + val of_int : IntDomain.IntDomTuple.t -> t val cast_to : Cil.ikind -> t -> IntDomain.IntDomTuple.t end diff --git a/src/util/cilfacade.ml b/src/util/cilfacade.ml index dc08b61138..b1cbf4ed51 100644 --- a/src/util/cilfacade.ml +++ b/src/util/cilfacade.ml @@ -339,6 +339,14 @@ let rec get_ikind t = Messages.warn "Something that we expected to be an integer type has a different type, assuming it is an IInt"; Cil.IInt +let get_fkind t = + (* important to unroll the type here, otherwise problems with typedefs *) + match Cil.unrollType t with + | TFloat (fk,_) -> fk + | _ -> + Messages.warn "Something that we expected to be a float type has a different type, assuming it is an FDouble"; + Cil.FDouble + let ptrdiff_ikind () = get_ikind !ptrdiffType @@ -458,6 +466,7 @@ and typeOffset basetyp = let get_ikind_exp e = get_ikind (typeOf e) +let get_fkind_exp e = get_fkind (typeOf e) (** HashSet of line numbers *) diff --git a/tests/regression/56-floats/05-invariant.c b/tests/regression/56-floats/05-invariant.c new file mode 100644 index 0000000000..2c6cae262a --- /dev/null +++ b/tests/regression/56-floats/05-invariant.c @@ -0,0 +1,99 @@ +// PARAM: --enable ana.float.interval --enable ana.int.interval +#include +#include + +int main() +{ + double a; + int b; + + // make a definitly finite! + if (b) + { + a = 100.; + } + else + { + a = -100.; + }; + + if (b != 1) + { + assert(b != 1); // SUCCESS! + } + else + { + assert(b == 1); // SUCCESS! + } + + if (a != 1.) + { + // this would require a exclusion list etc. + assert(a != 1.); // UNKNOWN! + } + + if (a == 1.) + { + assert(a == 1.); // SUCCESS! + } + + if ((int)a) + { + assert(a != 0.); // UNKNOWN! + } + if (a) // here a explicit cast to (int) should be inserted + { + assert(a != 0.); // UNKNOWN! + } + + if (a <= 5.) + { + assert(a <= 5.); // SUCCESS! + } + + if (a <= 5. && a >= -5.) + { + assert(a <= 5. && a >= -5.); // SUCCESS! + } + + if (a + 5. < 10.) + { + assert(a < 5.); // SUCCESS! + } + + if (a * 2. < 6.) + { + assert(a < 3.); // SUCCESS! + } + + if (a / 3. > 10.) + { + assert(a > 30.); // SUCCESS! + } + + if (((int)a) < 10) + { + assert(a < 10.); // SUCCESS! + } + + int c; + + if (0.5 < (double)c) + { + assert(0 < c); // SUCCESS! + assert(1 < c); // UNKNOWN! + assert(1 <= c); // SUCCESS! + } + + if (a > 1.) + { + assert(a < 1.); // FAIL! + if (a < 1.) + { + assert(0); // NOWARN + return 1; + } + } + + return 0; +} From e026bfa1aa45f404d45f8acba6290f55efa88532 Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Tue, 14 Jun 2022 16:59:09 +0200 Subject: [PATCH 15/54] Add unittests for our domain (#9) - lattice related functions (leq, widen, narrow, join, meet) - arithmetic operations (add, sub, mul, div) - casts (f2i, i2f) --- src/cdomains/floatDomain.ml | 2 +- src/cdomains/floatDomain.mli | 4 + unittest/cdomains/floatDomainTest.ml | 271 +++++++++++++++++++++++---- 3 files changed, 243 insertions(+), 34 deletions(-) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index eda7d5fcc2..3f41a59554 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -53,7 +53,7 @@ module FloatInterval = struct let show = function | None -> "Float[Top]" - | Some (low, high) -> "Float [" ^ string_of_float low ^ "," ^ string_of_float high ^ "]" + | Some (low, high) -> Printf.sprintf "Float [%.30f,%.30f]" low high let big_int_of_float f = let x, n = Float.frexp f in diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index e9a59c5a84..13871b0799 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -33,6 +33,7 @@ end module FloatInterval : sig (**Currently just for FloatDomainTest *) type t = (float * float) option include FloatArith with type t := t + include Lattice.S with type t := t val top : unit -> t @@ -42,6 +43,9 @@ module FloatInterval : sig (**Currently just for FloatDomainTest *) val of_const : float -> t val of_interval : float*float -> t + val of_int : IntDomain.IntDomTuple.t -> t + val cast_to : Cil.ikind -> t -> IntDomain.IntDomTuple.t + val ending : float -> t val starting : float -> t diff --git a/unittest/cdomains/floatDomainTest.ml b/unittest/cdomains/floatDomainTest.ml index 06da43a139..c04ae53fc0 100644 --- a/unittest/cdomains/floatDomainTest.ml +++ b/unittest/cdomains/floatDomainTest.ml @@ -1,91 +1,295 @@ open OUnit2 open Round - + module FloatInterval = struct module FI = FloatDomain.FloatInterval module IT = IntDomain.IntDomTuple - + + let fmax = Float.max_float + let fmin = -. Float.max_float + let fsmall = Float.min_float + let fi_zero = FI.of_const 0. let fi_one = FI.of_const 1. let fi_neg_one = FI.of_const (-.1.) let itb_true = IT.of_int IBool (Big_int_Z.big_int_of_int 1) let itb_false = IT.of_int IBool (Big_int_Z.big_int_of_int 0) let itb_unknown = IT.of_interval IBool (Big_int_Z.big_int_of_int 0, Big_int_Z.big_int_of_int 1) - + let assert_equal v1 v2 = assert_equal ~cmp:FI.equal ~printer:FI.show v1 v2 - + let itb_xor v1 v2 = (**returns true, if both IntDomainTuple bool results are either unknown or different *) ((IT.equal v1 itb_unknown) && (IT.equal v2 itb_unknown)) || ((IT.equal v1 itb_true) && (IT.equal v2 itb_false)) || ((IT.equal v1 itb_false) && (IT.equal v2 itb_true)) - + (**interval tests *) let test_FI_nan _ = assert_equal (FI.top ()) (FI.of_const Float.nan) - - let test_FI_add1 _ = - assert_equal fi_one (FI.add fi_zero fi_one) - - let test_FI_add2 _ = - assert_equal fi_zero (FI.add fi_one fi_neg_one) - + + let test_FI_add_specific _ = + let (+) = FI.add in + let (=) a b = assert_equal b a in + begin + (FI.of_const (-. 0.)) = fi_zero; + fi_zero + fi_one = fi_one; + fi_neg_one + fi_one = fi_zero; + fi_one + (FI.of_const fmax) = None; + fi_neg_one + (FI.of_const fmin) = None; + fi_neg_one + (FI.of_const fmax) = (FI.of_interval ((Float.pred fmax), fmax)); + fi_one + (FI.of_const fmin) = (FI.of_interval (fmin, Float.succ fmin)); + FI.top () + FI.top () = None; + (FI.of_const fmin) + (FI.of_const fmax) = fi_zero; + (FI.of_const fsmall) + (FI.of_const fsmall) = FI.of_const (fsmall +. fsmall); + (FI.of_const fsmall) + (FI.of_const 1.) = FI.of_interval (1., Float.succ (1. +. fsmall)); + (FI.of_interval (1., 2.)) + (FI.of_interval (2., 3.)) = FI.of_interval (3., 5.); + (FI.of_interval (-. 2., 3.)) + (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 102., 23.); + end + + let test_FI_sub_specific _ = + let (-) = FI.sub in + let (=) a b = assert_equal b a in + begin + fi_zero - fi_one = fi_neg_one; + fi_neg_one - fi_one = FI.of_const (-. 2.); + fi_one - (FI.of_const fmin) = None; + fi_neg_one - (FI.of_const fmax) = None; + (FI.of_const fmax) - fi_one = (FI.of_interval ((Float.pred fmax), fmax)); + (FI.of_const fmin) - fi_neg_one = (FI.of_interval (fmin, Float.succ fmin)); + FI.top () - FI.top () = None; + (FI.of_const fmax) - (FI.of_const fmax) = fi_zero; + (FI.of_const fsmall) - (FI.of_const fsmall) = fi_zero; + (FI.of_const fsmall) - (FI.of_const 1.) = FI.of_interval (-. 1., Float.succ (-. 1.)); + (FI.of_interval (-. 2., 3.)) - (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 22., 103.); + (FI.of_const (-. 0.)) - fi_zero = fi_zero + end + + let test_FI_mul_specific _ = + let ( * ) = FI.mul in + let (=) a b = assert_equal b a in + begin + fi_zero * fi_one = fi_zero; + (FI.of_const 2.) * (FI.of_const fmin) = None; + (FI.of_const 2.) * (FI.of_const fmax) = None; + (FI.of_const fsmall) * (FI.of_const fmax) = FI.of_const (fsmall *. fmax); + FI.top () * FI.top () = None; + (FI.of_const fmax) * fi_zero = fi_zero; + (FI.of_const fsmall) * fi_zero = fi_zero; + (FI.of_const fsmall) * fi_one = FI.of_const fsmall; + (FI.of_const fmax) * fi_one = FI.of_const fmax; + (FI.of_const 2.) * (FI.of_const 0.5) = fi_one; + (FI.of_interval (-. 2., 3.)) * (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 300., 200.); + (FI.of_const 1.00000000000000111) * (FI.of_const 1.00000000000000111) = FI.of_interval (1.00000000000000222 , Float.succ 1.00000000000000222); + (FI.of_const (-. 1.00000000000000111)) * (FI.of_const 1.00000000000000111) = FI.of_interval (Float.pred (-. 1.00000000000000222), -. 1.00000000000000222) + end + + let test_FI_div_specific _ = + let (/) = FI.div in + let (=) a b = assert_equal b a in + begin + fi_zero / fi_one = fi_zero; + (FI.of_const 2.) / fi_zero = None; + fi_zero / fi_zero = None; + (FI.of_const fmax) / (FI.of_const fsmall) = None; + (FI.of_const fmin) / (FI.of_const fsmall) = None; + FI.top () / FI.top () = None; + fi_zero / fi_one = fi_zero; + (FI.of_const fsmall) / fi_one = FI.of_const fsmall; + (FI.of_const fsmall) / (FI.of_const fsmall) = fi_one; + (FI.of_const fmax) / (FI.of_const fmax) = fi_one; + (FI.of_const fmax) / fi_one = FI.of_const fmax; + (FI.of_const 2.) / (FI.of_const 0.5) = (FI.of_const 4.); + (FI.of_const 4.) / (FI.of_const 2.) = (FI.of_const 2.); + (FI.of_interval (-. 2., 3.)) / (FI.of_interval (-. 100., 20.)) = None; + (FI.of_interval (6., 10.)) / (FI.of_interval (2., 3.)) = (FI.of_interval (2., 5.)); + + (FI.of_const 1.00000000000000111) / (FI.of_const 1.00000000000000111) = fi_one; + (FI.of_const 1.) / (FI.of_const 3.) = (FI.of_interval (Float.pred 0.333333333333333370340767487505, 0.333333333333333370340767487505)); + (FI.of_const (-. 1.)) / (FI.of_const 3.) = (FI.of_interval (-. 0.333333333333333370340767487505, Float.succ (-. 0.333333333333333370340767487505))) + end + + let test_FI_casti2f_specific _ = + let cast_bool a b = + assert_equal b (FI.of_int (IT.of_int IBool (Big_int_Z.big_int_of_int a))) in + begin + cast_bool 0 fi_zero; + cast_bool 1 fi_one + end; + let cast a b = assert_equal b (FI.of_int a) in + begin + GobConfig.set_bool "ana.int.interval" true; + cast (IT.top_of IInt) (FI.of_interval (-2147483648.,2147483647.)); + cast (IT.top_of IBool) (FI.of_interval (0., 1.)); + cast (IT.of_int IInt Big_int_Z.zero_big_int) fi_zero; + cast (IT.of_int IInt Big_int_Z.unit_big_int) fi_one; + cast (IT.of_interval IUChar (Big_int_Z.big_int_of_int 0, Big_int_Z.big_int_of_int 128)) (FI.of_interval (0., 128.)); + cast (IT.of_interval IChar (Big_int_Z.big_int_of_int (-8), Big_int_Z.big_int_of_int (-1))) (FI.of_interval (-. 8., - 1.)); + cast (IT.of_interval IUInt (Big_int_Z.big_int_of_int 2, Big_int_Z.big_int_of_int 100)) (FI.of_interval (2., 100.)); + cast (IT.of_interval IInt (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)) (FI.of_interval (-. 100., 100.)); + cast (IT.of_interval IUShort (Big_int_Z.big_int_of_int 2, Big_int_Z.big_int_of_int 100)) (FI.of_interval (2., 100.)); + cast (IT.of_interval IShort (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)) (FI.of_interval (-. 100., 100.)); + + cast (IT.of_interval IULong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_int64 Int64.max_int)) (FI.of_interval (0., 9223372036854775807.)); + cast (IT.of_interval IULong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_int64 (9223372036854775806L))) (FI.of_interval (0., 9223372036854775807.)); + cast (IT.of_interval ILong (Big_int_Z.big_int_of_int64 Int64.min_int, Big_int_Z.zero_big_int)) (FI.of_interval (-. 9223372036854775808., 0.)); + cast (IT.of_interval ILong (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)) (FI.of_interval (-. 100., 100.)); + cast (IT.of_interval IULongLong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_int64 Int64.max_int)) (FI.of_interval (0., 9223372036854775807.)); + cast (IT.of_interval IULongLong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_int64 (9223372036854775806L))) (FI.of_interval (0., 9223372036854775807.)); + cast (IT.of_interval ILongLong (Big_int_Z.big_int_of_int64 Int64.min_int, Big_int_Z.zero_big_int)) (FI.of_interval (-. 9223372036854775808., 0.)); + cast (IT.of_interval ILongLong (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)) (FI.of_interval (-. 100., 100.)); + GobConfig.set_bool "ana.int.interval" false; + end + + let test_FI_castf2i_specific _ = + let cast ikind a b = + OUnit2.assert_equal ~cmp:IT.equal ~printer:IT.show b (FI.cast_to ikind a) in + begin + GobConfig.set_bool "ana.int.interval" true; + cast IInt (FI.of_interval (-2147483648.,2147483647.)) (IT.top_of IInt); + cast IInt (FI.of_interval (-9999999999.,9999999999.)) (IT.top_of IInt); + cast IInt (FI.of_interval (-10.1,20.9)) (IT.of_interval IInt ( Big_int_Z.big_int_of_int (-10), Big_int_Z.big_int_of_int 20)); + cast IBool (FI.of_interval (0.,1.)) (IT.top_of IBool); + cast IBool (FI.of_interval (-9999999999.,9999999999.)) (IT.top_of IBool); + cast IBool fi_one (IT.of_bool IBool true); + cast IBool fi_zero (IT.of_bool IBool false); + + cast IUChar (FI.of_interval (0.123, 128.999)) (IT.of_interval IUChar (Big_int_Z.big_int_of_int 0, Big_int_Z.big_int_of_int 128)); + cast IChar (FI.of_interval (-. 8.0000000, 127.)) (IT.of_interval IChar (Big_int_Z.big_int_of_int (-8), Big_int_Z.big_int_of_int 127)); + cast IUInt (FI.of_interval (2., 100.)) (IT.of_interval IUInt (Big_int_Z.big_int_of_int 2, Big_int_Z.big_int_of_int 100)); + cast IInt (FI.of_interval (-. 100.2, 100.1)) (IT.of_interval IInt (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); + cast IUShort (FI.of_interval (2., 100.)) (IT.of_interval IUShort (Big_int_Z.big_int_of_int 2, Big_int_Z.big_int_of_int 100)); + cast IShort (FI.of_interval (-. 100., 100.)) (IT.of_interval IShort (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); + + cast IULong (FI.of_interval (0., 9223372036854775808.)) (IT.of_interval IULong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_string "9223372036854775808")); + cast ILong (FI.of_interval (-. 9223372036854775808., 0.)) (IT.of_interval ILong (Big_int_Z.big_int_of_string "-9223372036854775808", Big_int_Z.zero_big_int)); + cast ILong (FI.of_interval (-. 100.99999, 100.99999)) (IT.of_interval ILong (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); + cast IULongLong (FI.of_interval (0., 9223372036854775808.)) (IT.of_interval IULongLong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_string "9223372036854775808")); + cast ILongLong (FI.of_interval (-. 9223372036854775808., 0.)) (IT.of_interval ILongLong ((Big_int_Z.big_int_of_string "-9223372036854775808"), Big_int_Z.zero_big_int)); + cast ILongLong (FI.of_interval (-. 100., 100.)) (IT.of_interval ILongLong (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); + GobConfig.set_bool "ana.int.interval" false; + end + + let test_FI_meet_specific _ = + let check_meet a b c = + assert_equal c (FI.meet a b) in + begin + check_meet (FI.top ()) (FI.top ()) (FI.top ()); + check_meet (FI.top ()) fi_one fi_one; + assert_raises (Failure "meet results in empty Interval") (fun () -> (FI.meet fi_zero fi_one)); + check_meet (FI.of_interval (0., 10.)) (FI.of_interval (5., 20.)) (FI.of_interval (5., 10.)); + end + + let test_FI_join_specific _ = + let check_join a b c = + assert_equal c (FI.join a b) in + begin + check_join (FI.top ()) (FI.top ()) (FI.top ()); + check_join (FI.top ()) fi_one (FI.top ()); + check_join (FI.of_interval (0., 10.)) (FI.of_interval (5., 20.)) (FI.of_interval (0., 20.)); + end + + let test_FI_leq_specific _ = + let check_leq flag a b = + OUnit2.assert_equal flag (FI.leq a b) in + begin + check_leq true (FI.top ()) (FI.top ()); + check_leq true fi_one fi_one; + check_leq false fi_one fi_zero; + check_leq true (FI.of_interval (5., 20.)) (FI.of_interval (0., 20.)); + check_leq false (FI.of_interval (0., 20.)) (FI.of_interval (5., 20.)); + check_leq true (FI.of_interval (1., 19.)) (FI.of_interval (0., 20.)); + check_leq false (FI.of_interval (0., 20.)) (FI.of_interval (20.0001, 20.0002)); + end + + let test_FI_widen_specific _ = + let check_widen a b c = + assert_equal c (FI.widen a b) in + begin + check_widen (FI.top ()) (FI.top ()) (FI.top ()); + check_widen fi_zero (FI.top ()) (FI.top ()); + check_widen (FI.top ()) fi_one (FI.top ()); + check_widen fi_zero fi_one (FI.of_interval (0., Float.max_float)); + check_widen fi_one fi_zero (FI.of_interval (-. Float.max_float, 1.)); + check_widen fi_one (FI.of_interval (0., 2.)) (FI.of_interval (-. Float.max_float, Float.max_float)); + end + + let test_FI_narrow_specific _ = + let check_narrow a b c = + assert_equal c (FI.narrow a b) in + begin + check_narrow (FI.top ()) (FI.top ()) (FI.top ()); + check_narrow fi_zero (FI.top ()) fi_zero; + check_narrow (FI.top ()) fi_zero fi_zero; + check_narrow fi_zero fi_one fi_zero; + end + (**TODO: add tests for specific edge cases (eg. overflow to infinity when dbl.max + 1) *) - + (**interval tests using QCheck arbitraries *) let test_FI_not_bot = QCheck.Test.make ~name:"test_FI_not_bot" (FI.arbitrary ()) (fun arg -> not (FI.is_bot arg)) - + let test_FI_of_const_not_bot = QCheck.Test.make ~name:"test_FI_of_const_not_bot" QCheck.float (fun arg -> not (FI.is_bot (FI.of_const arg))) - + let test_FI_div_zero_result_top = QCheck.Test.make ~name:"test_FI_div_zero_result_top" (FI.arbitrary ()) (fun arg -> FI.is_top (FI.div arg (FI.of_const 0.))) - + let test_FI_accurate_neg = QCheck.Test.make ~name:"test_FI_accurate_neg" QCheck.float (fun arg -> FI.equal (FI.of_const (-.arg)) (FI.neg (FI.of_const arg))) - + let test_FI_lt_xor_ge = QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> itb_xor (FI.lt arg1 arg2) (FI.ge arg1 arg2)) - + let test_FI_gt_xor_le = QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> itb_xor (FI.gt arg1 arg2) (FI.le arg1 arg2)) - + let test_FI_eq_xor_ne = QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> itb_xor (FI.eq arg1 arg2) (FI.ne arg1 arg2)) - + let test_FI_add = QCheck.Test.make ~name:"test_FI_add" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.add (FI.of_const arg1) (FI.of_const arg2) in (FI.leq (FI.of_const (add Up arg1 arg2)) result) && (FI.leq (FI.of_const (add Down arg1 arg2)) result)) - + let test_FI_sub = QCheck.Test.make ~name:"test_FI_sub" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.sub (FI.of_const arg1) (FI.of_const arg2) in (FI.leq (FI.of_const (sub Up arg1 arg2)) result) && (FI.leq (FI.of_const (sub Down arg1 arg2)) result)) - + let test_FI_mul = QCheck.Test.make ~name:"test_FI_mul" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.mul (FI.of_const arg1) (FI.of_const arg2) in (FI.leq (FI.of_const (mul Up arg1 arg2)) result) && (FI.leq (FI.of_const (mul Down arg1 arg2)) result)) - + let test_FI_div = QCheck.Test.make ~name:"test_FI_div" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.div (FI.of_const arg1) (FI.of_const arg2) in (FI.leq (FI.of_const (div Up arg1 arg2)) result) && (FI.leq (FI.of_const (div Down arg1 arg2)) result)) - + let test () = [ "test_FI_nan" >:: test_FI_nan; - "test_FI_add1" >:: test_FI_add1; - "test_FI_add2" >:: test_FI_add2; + "test_FI_add_specific" >:: test_FI_add_specific; + "test_FI_sub_specific" >:: test_FI_sub_specific; + "test_FI_mul_specific" >:: test_FI_mul_specific; + "test_FI_div_specific" >:: test_FI_div_specific; + "test_FI_casti2f_specific" >:: test_FI_casti2f_specific; + "test_FI_castf2i_specific" >:: test_FI_castf2i_specific; + (* "test_FI_castf2f_specific" >:: *) + "test_FI_join_specific" >:: test_FI_meet_specific; + "test_FI_meet_specific" >:: test_FI_join_specific; + "test_FI_meet_specific" >:: test_FI_leq_specific; + "test_FI_widen_specific" >:: test_FI_widen_specific; + "test_FI_narrow_specific" >:: test_FI_narrow_specific; ] - + let test_qcheck () = QCheck_ounit.to_ounit2_test_list [ test_FI_not_bot; test_FI_of_const_not_bot; @@ -100,9 +304,10 @@ struct test_FI_div; ] end - -let test () = "floatDomainTest" >::: - [ - "float_interval" >::: FloatInterval.test (); - "float_interval_qcheck" >::: FloatInterval.test_qcheck (); - ] \ No newline at end of file + +let test () = + "floatDomainTest" >::: + [ + "float_interval" >::: FloatInterval.test (); + "float_interval_qcheck" >::: FloatInterval.test_qcheck (); + ] From 1b96fe428830f26173f8b3e4c33d348647a8de59 Mon Sep 17 00:00:00 2001 From: Dominik Berger Date: Tue, 14 Jun 2022 17:08:07 +0200 Subject: [PATCH 16/54] Add all math.h functions in libraryFunctions.ml with readsAll (#12) --- src/analyses/libraryFunctions.ml | 177 +++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/src/analyses/libraryFunctions.ml b/src/analyses/libraryFunctions.ml index be3875c4a7..177649e105 100644 --- a/src/analyses/libraryFunctions.ml +++ b/src/analyses/libraryFunctions.ml @@ -521,6 +521,183 @@ let invalidate_actions = [ "down_trylock", readsAll; "up", readsAll; "ZSTD_customFree", frees [1]; (* only used with extraspecials *) + "acos", readsAll; + "acosf", readsAll; + "acosh", readsAll; + "acoshf", readsAll; + "acoshl", readsAll; + "acosl", readsAll; + "asin", readsAll; + "asinf", readsAll; + "asinh", readsAll; + "asinhf", readsAll; + "asinhl", readsAll; + "asinl", readsAll; + "atan", readsAll; + "atan2", readsAll; + "atan2f", readsAll; + "atan2l", readsAll; + "atanf", readsAll; + "atanh", readsAll; + "atanhf", readsAll; + "atanhl", readsAll; + "atanl", readsAll; + "cbrt", readsAll; + "cbrtf", readsAll; + "cbrtl", readsAll; + "ceil", readsAll; + "ceilf", readsAll; + "ceill", readsAll; + "copysign", readsAll; + "copysignf", readsAll; + "copysignl", readsAll; + "cos", readsAll; + "cosf", readsAll; + "cosh", readsAll; + "coshf", readsAll; + "coshl", readsAll; + "cosl", readsAll; + "erf", readsAll; + "erfc", readsAll; + "erfcf", readsAll; + "erfcl", readsAll; + "erff", readsAll; + "erfl", readsAll; + "exp", readsAll; + "exp2", readsAll; + "exp2f", readsAll; + "exp2l", readsAll; + "expf", readsAll; + "expl", readsAll; + "expm1", readsAll; + "expm1f", readsAll; + "expm1l", readsAll; + "fabs", readsAll; + "fabsf", readsAll; + "fabsl", readsAll; + "fdim", readsAll; + "fdimf", readsAll; + "fdiml", readsAll; + "floor", readsAll; + "floorf", readsAll; + "floorl", readsAll; + "fma", readsAll; + "fmaf", readsAll; + "fmal", readsAll; + "fmax", readsAll; + "fmaxf", readsAll; + "fmaxl", readsAll; + "fmin", readsAll; + "fminf", readsAll; + "fminl", readsAll; + "fmod", readsAll; + "fmodf", readsAll; + "fmodl", readsAll; + "frexp", readsAll; + "frexpf", readsAll; + "frexpl", readsAll; + "hypot", readsAll; + "hypotf", readsAll; + "hypotl", readsAll; + "ilogb", readsAll; + "ilogbf", readsAll; + "ilogbl", readsAll; + "j0", readsAll; + "j1", readsAll; + "jn", readsAll; + "ldexp", readsAll; + "ldexpf", readsAll; + "ldexpl", readsAll; + "lgamma", readsAll; + "lgammaf", readsAll; + "lgammal", readsAll; + "llrint", readsAll; + "llrintf", readsAll; + "llrintl", readsAll; + "llround", readsAll; + "llroundf", readsAll; + "llroundl", readsAll; + "log", readsAll; + "log10", readsAll; + "log10f", readsAll; + "log10l", readsAll; + "log1p", readsAll; + "log1pf", readsAll; + "log1pl", readsAll; + "log2", readsAll; + "log2f", readsAll; + "log2l", readsAll; + "logb", readsAll; + "logbf", readsAll; + "logbl", readsAll; + "logf", readsAll; + "logl", readsAll; + "lrint", readsAll; + "lrintf", readsAll; + "lrintl", readsAll; + "lround", readsAll; + "lroundf", readsAll; + "lroundl", readsAll; + "modf", readsAll; + "modff", readsAll; + "modfl", readsAll; + "nan", readsAll; + "nanf", readsAll; + "nanl", readsAll; + "nearbyint", readsAll; + "nearbyintf", readsAll; + "nearbyintl", readsAll; + "nextafter", readsAll; + "nextafterf", readsAll; + "nextafterl", readsAll; + "nexttoward", readsAll; + "nexttowardf", readsAll; + "nexttowardl", readsAll; + "pow", readsAll; + "powf", readsAll; + "powl", readsAll; + "remainder", readsAll; + "remainderf", readsAll; + "remainderl", readsAll; + "remquo", readsAll; + "remquof", readsAll; + "remquol", readsAll; + "rint", readsAll; + "rintf", readsAll; + "rintl", readsAll; + "round", readsAll; + "roundf", readsAll; + "roundl", readsAll; + "scalbln", readsAll; + "scalblnf", readsAll; + "scalblnl", readsAll; + "scalbn", readsAll; + "scalbnf", readsAll; + "scalbnl", readsAll; + "sin", readsAll; + "sinf", readsAll; + "sinh", readsAll; + "sinhf", readsAll; + "sinhl", readsAll; + "sinl", readsAll; + "sqrt", readsAll; + "sqrtf", readsAll; + "sqrtl", readsAll; + "tan", readsAll; + "tanf", readsAll; + "tanh", readsAll; + "tanhf", readsAll; + "tanhl", readsAll; + "tanl", readsAll; + "tgamma", readsAll; + "tgammaf", readsAll; + "tgammal", readsAll; + "trunc", readsAll; + "truncf", readsAll; + "truncl", readsAll; + "y0", readsAll; + "y1", readsAll; + "yn", readsAll; ] (* used by get_invalidate_action to make sure From eadd02fdc921471a18b85c35605a6ff192ba01d4 Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Wed, 15 Jun 2022 13:14:20 +0200 Subject: [PATCH 17/54] Implement float operators in C / Support Float and Double (#10) * Support FFloat and FDouble - Implement float operators in C - Support FFloat and FDouble * Allow using DBL_MIN, DBL_MAX Some compilers specifiy DBL_MIN, DBL_MAX as long double. As we do not support long double in neither cil nor goblint itself, but want to use DBL_MIN, DBL_MAX, we have no other option than to explicitly check for this two cases --- dune-project | 1 - goblint.opam | 2 - goblint.opam.locked | 5 - goblint.opam.template | 1 - src/analyses/base.ml | 62 +-- src/cdomains/floatDomain.ml | 384 ++++++++++++------ src/cdomains/floatDomain.mli | 59 +-- src/cdomains/floatOps/floatOps.ml | 104 +++++ src/cdomains/floatOps/floatOps.mli | 40 ++ src/cdomains/floatOps/stubs.c | 87 ++++ src/cdomains/valueDomain.ml | 20 +- src/dune | 3 +- tests/regression/56-floats/01-base.c | 21 +- .../regression/56-floats/03-infinity_or_nan.c | 8 +- tests/regression/56-floats/04-casts.c | 55 ++- tests/regression/56-floats/05-invariant.c | 59 +-- unittest/cdomains/floatDomainTest.ml | 182 +++++---- 17 files changed, 759 insertions(+), 334 deletions(-) create mode 100644 src/cdomains/floatOps/floatOps.ml create mode 100644 src/cdomains/floatOps/floatOps.mli create mode 100644 src/cdomains/floatOps/stubs.c diff --git a/dune-project b/dune-project index d4b8137414..c142b805c7 100644 --- a/dune-project +++ b/dune-project @@ -34,7 +34,6 @@ ppx_deriving_hash ppx_deriving_yojson (ppx_blob (>= 0.6.0)) - round (ocaml-monadic (>= 0.5)) (ounit2 :with-test) (qcheck-ounit :with-test) diff --git a/goblint.opam b/goblint.opam index 3383ecea3a..32260d4324 100644 --- a/goblint.opam +++ b/goblint.opam @@ -30,7 +30,6 @@ depends: [ "ppx_deriving_hash" "ppx_deriving_yojson" "ppx_blob" {>= "0.6.0"} - "round" "ocaml-monadic" {>= "0.5"} "ounit2" {with-test} "qcheck-ounit" {with-test} @@ -74,5 +73,4 @@ pin-depends: [ # [ "ppx_deriving_yojson.3.6.1" "git+https://github.com/ocaml-ppx/ppx_deriving_yojson.git#e030f13a3450e9cf7d2c43fa04e709ef608486cd" ] # TODO: add back after release, only pinned for CI stability [ "apron.v0.9.13" "git+https://github.com/antoinemine/apron.git#c852ebcc89e5cf4a5a3318e7c13c73e1756abb11"] - [ "round.0.1" "git+https://github.com/brgr/ocaml-round.git#2dfe43050bfdac885607b6697c19c2ed913d5be4"] ] diff --git a/goblint.opam.locked b/goblint.opam.locked index 8c5050fac1..4ae124b8cc 100644 --- a/goblint.opam.locked +++ b/goblint.opam.locked @@ -80,7 +80,6 @@ depends: [ "qcheck-ounit" {= "0.18.1" & with-test} "re" {= "1.10.3" & with-doc} "result" {= "1.5"} - "round" {= "0.1"} "seq" {= "base" & with-test} "sexplib0" {= "v0.14.0"} "sha" {= "1.15.2"} @@ -127,8 +126,4 @@ pin-depends: [ "ppx_deriving.5.2.1" "git+https://github.com/ocaml-ppx/ppx_deriving.git#0a89b619f94cbbfc3b0fb3255ab4fe5bc77d32d6" ] - [ - "round.0.1" - "git+https://github.com/brgr/ocaml-round.git#2dfe43050bfdac885607b6697c19c2ed913d5be4" - ] ] diff --git a/goblint.opam.template b/goblint.opam.template index 004b35f800..5813c9f0ae 100644 --- a/goblint.opam.template +++ b/goblint.opam.template @@ -8,5 +8,4 @@ pin-depends: [ # [ "ppx_deriving_yojson.3.6.1" "git+https://github.com/ocaml-ppx/ppx_deriving_yojson.git#e030f13a3450e9cf7d2c43fa04e709ef608486cd" ] # TODO: add back after release, only pinned for CI stability [ "apron.v0.9.13" "git+https://github.com/antoinemine/apron.git#c852ebcc89e5cf4a5a3318e7c13c73e1756abb11"] - [ "round.0.1" "git+https://github.com/brgr/ocaml-round.git#2dfe43050bfdac885607b6697c19c2ed913d5be4"] ] diff --git a/src/analyses/base.ml b/src/analyses/base.ml index cffdf2d6c4..15756b34ea 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -132,7 +132,7 @@ struct let unop_FD = function | Neg -> FD.neg (* other unary operators are not implemented on float values *) - | _ -> (fun _ -> FD.top ()) + | _ -> (fun c -> FD.top_of (FD.precision c)) (* Evaluating Cil's unary operators. *) let evalunop op typ = function @@ -169,12 +169,12 @@ struct | LOr -> ID.logor | b -> (fun x y -> (ID.top_of result_ik)) - let binop_FD = function + let binop_FD (result_fk: Cil.fkind) = function | PlusA -> FD.add | MinusA -> FD.sub | Mult -> FD.mul | Div -> FD.div - | _ -> (fun _ _ -> FD.top ()) + | _ -> (fun _ _ -> FD.top_of result_fk) let int_returning_binop_FD = function | Lt -> FD.lt @@ -262,7 +262,7 @@ struct | `Float v1, `Float v2 when is_int_returning_binop_FD op -> let result_ik = Cilfacade.get_ikind t in `Int (ID.cast_to result_ik (int_returning_binop_FD op v1 v2)) - | `Float v1, `Float v2 -> `Float (binop_FD op v1 v2) + | `Float v1, `Float v2 -> `Float (binop_FD (Cilfacade.get_fkind t) op v1 v2) (* For address +/- value, we try to do some elementary ptr arithmetic *) | `Address p, `Int n | `Int n, `Address p when op=Eq || op=Ne -> @@ -727,7 +727,11 @@ struct | Const (CInt (num,ikind,str)) -> (match str with Some x -> M.tracel "casto" "CInt (%s, %a, %s)\n" (Cilint.string_of_cilint num) d_ikind ikind x | None -> ()); `Int (ID.cast_to ikind (IntDomain.of_const (num,ikind,str))) - | Const (CReal (num, FDouble, _)) -> `Float (FD.of_const num) (* TODO(Practical(2022): use string representation instead, extend to other floating point types *) + | Const (CReal (_, (FFloat | FDouble as fkind), Some str)) -> `Float (FD.of_string fkind str) (* prefer parsing from string due to higher precision *) + | Const (CReal (num, (FFloat | FDouble as fkind), None)) -> `Float (FD.of_const fkind num) + (* this is so far only for DBL_MIN/DBL_MAX as it is represented as LongDouble although it would fit into a double as well *) + | Const (CReal (_, (FLongDouble), Some str)) when str = "2.2250738585072014e-308L" -> `Float (FD.of_string FDouble str) + | Const (CReal (_, (FLongDouble), Some str)) when str = "1.7976931348623157e+308L" -> `Float (FD.of_string FDouble str) (* String literals *) | Const (CStr (x,_)) -> `Address (AD.from_string x) (* normal 8-bit strings, type: char* *) | Const (CWStr (xs,_) as c) -> (* wide character strings, type: wchar_t* *) @@ -1641,9 +1645,7 @@ struct | PlusA -> meet_com FD.sub | Mult -> (* refine x by information about y, using x * y == c *) - let refine_by x y = (match FD.to_float y with - | None -> x - | Some _ -> FD.meet x (FD.div c y)) + let refine_by x y = if FD.is_exact y then FD.meet x (FD.div c y) else x in (refine_by a b, refine_by b a) | MinusA -> meet_non FD.add FD.sub @@ -1653,7 +1655,7 @@ struct | Eq | Ne as op -> let both x = x, x in let m = FD.meet a b in - let result = FD.ne c (FD.of_const 0.) in + let result = FD.ne c (FD.of_const (FD.precision c) 0.) in (match op, ID.to_bool(result) with | Eq, Some true | Ne, Some false -> both m (* def. equal: if they compare equal, both values must be from the meet *) @@ -1666,15 +1668,15 @@ struct | Lt | Le | Ge | Gt as op -> (match FD.minimal a, FD.maximal a, FD.minimal b, FD.maximal b with | Some l1, Some u1, Some l2, Some u2 -> - (match op, ID.to_bool(FD.ne c (FD.of_const 0.)) with + (match op, ID.to_bool(FD.ne c (FD.of_const (FD.precision c) 0.)) with | Le, Some true - | Gt, Some false -> meet_bin (FD.ending u2) (FD.starting l1) + | Gt, Some false -> meet_bin (FD.ending (FD.precision a) u2) (FD.starting (FD.precision b) l1) | Ge, Some true - | Lt, Some false -> meet_bin (FD.starting l2) (FD.ending u1) + | Lt, Some false -> meet_bin (FD.starting (FD.precision a) l2) (FD.ending (FD.precision b) u1) | Lt, Some true - | Ge, Some false -> meet_bin (FD.ending (Float.pred u2)) (FD.starting (Float.succ l1)) + | Ge, Some false -> meet_bin (FD.ending (FD.precision a) (Float.pred u2)) (FD.starting (FD.precision b) (Float.succ l1)) | Gt, Some true - | Le, Some false -> meet_bin (FD.starting (Float.succ l2)) (FD.ending (Float.pred u1)) + | Le, Some false -> meet_bin (FD.starting (FD.precision a) (Float.succ l2)) (FD.ending (FD.precision b) (Float.pred u1)) | _, _ -> a, b) | _ -> a, b) | op -> @@ -1707,7 +1709,7 @@ struct inv_exp (`Int c') e st | UnOp (Neg, e, _), `Float c -> inv_exp (`Float (unop_FD Neg c)) e st | UnOp ((BNot|Neg) as op, e, _), `Int c -> inv_exp (`Int (unop_ID op c)) e st - (* no equivalent for `Float so far, as VD.is_safe_cast fails for all float types anyways *) + (* no equivalent for `Float, as VD.is_safe_cast fails for all float types anyways *) | BinOp(op, CastE (t1, c1), CastE (t2, c2), t), `Int c when (op = Eq || op = Ne) && typeSig (Cilfacade.typeOf c1) = typeSig (Cilfacade.typeOf c2) && VD.is_safe_cast t1 (Cilfacade.typeOf c1) && VD.is_safe_cast t2 (Cilfacade.typeOf c2) -> inv_exp (`Int c) (BinOp (op, c1, c2, t)) st | (BinOp (op, e1, e2, _) as e, `Float _) @@ -1723,21 +1725,21 @@ struct let st'' = inv_exp (`Int b') e2 st' in st'' | `Float a, `Float b -> - let fkind = Cilfacade.get_fkind_exp e1 in (* both operands have the same type (except for Shiftlt, Shiftrt)! *) + let fkind = Cilfacade.get_fkind_exp e1 in (* both operands have the same type *) let a', b' = inv_bin_float (a, b) fkind (c_float fkind) op in if M.tracing then M.tracel "inv" "binop: %a, a': %a, b': %a\n" d_exp e FD.pretty a' FD.pretty b'; let st' = inv_exp (`Float a') e1 st in let st'' = inv_exp (`Float b') e2 st' in st'' - (* Mixed `Float and `Int cases should never happen, TODO: make this sure ?!*) - | `Int _, `Float _ | `Float _, `Int _ -> failwith "todo - probably ill-typed program"; + (* Mixed `Float and `Int cases should never happen, as there are no binary operators with one float and one int parameter ?!*) + | `Int _, `Float _ | `Float _, `Int _ -> failwith "ill-typed program"; (* | `Address a, `Address b -> ... *) | a1, a2 -> fallback ("binop: got abstract values that are not `Int: " ^ sprint VD.pretty a1 ^ " and " ^ sprint VD.pretty a2) st) (* use closures to avoid unused casts *) in (match c_typed with - | `Int c -> invert_binary_op c ID.pretty (fun _ -> c) (fun _ -> FD.of_int c) - | `Float c -> invert_binary_op c FD.pretty (fun ikind -> FD.cast_to ikind c) (fun _ -> c) - | _ -> failwith "unreachable") + | `Int c -> invert_binary_op c ID.pretty (fun ik -> ID.cast_to ik c) (fun fk -> FD.of_int fk c) + | `Float c -> invert_binary_op c FD.pretty (fun ik -> FD.to_int ik c) (fun fk -> FD.cast_to fk c) + | _ -> failwith "unreachable") | Lval x, `Int _(* meet x with c *) | Lval x, `Float _ -> (* meet x with c *) let update_lval c x c' pretty = (match x with @@ -1767,19 +1769,24 @@ struct | TPtr _ -> `Address (AD.of_int (module ID) c) | TInt (ik, _) | TEnum ({ekind = ik; _}, _) -> `Int (ID.cast_to ik c ) - | TFloat (fk, _) -> `Float (FD.of_int c) + | TFloat (fk, _) -> `Float (FD.of_int fk c) | _ -> `Int c) ID.pretty | `Float c -> update_lval c x (match t with (* | TPtr _ -> ..., pointer conversion from/to float is not supported *) - | TInt (ik, _) -> `Int (FD.cast_to ik c) + | TInt (ik, _) -> `Int (FD.to_int ik c) (* this is theoretically possible and should be handled correctly, however i can't imagine an actual piece of c code producing this?! *) - | TEnum ({ekind = ik; _}, _) -> `Int (FD.cast_to ik c) - | TFloat (fk, _) -> `Float c + | TEnum ({ekind = ik; _}, _) -> `Int (FD.to_int ik c) + | TFloat (fk, _) -> `Float (FD.cast_to fk c) | _ -> `Float c) FD.pretty | _ -> failwith "unreachable") | Const _ , _ -> st (* nothing to do *) - | CastE ((TFloat (FDouble, _)), e), `Float c -> inv_exp (`Float c) e st - | CastE ((TFloat (fk, _)), e), `Float c -> failwith "todo - f2f casts" (* TODO(Practical2022): extend to other floating point types *) + | CastE ((TFloat (_, _)), e), `Float c -> + (match Cilfacade.typeOf e, FD.precision c with + | TFloat (FDouble as fk, _), FFloat + | TFloat (FDouble as fk, _), FDouble + | TFloat (FFloat as fk, _), FFloat -> inv_exp (`Float (FD.cast_to fk c)) e st + | TFloat (FFloat, _), FDouble -> fallback ("CastE: e evaluates to FFloat which can not be used as FDouble") st + | _ -> fallback ("CastE: e has not type TFloat") st) | CastE ((TInt (ik, _)) as t, e), `Int c | CastE ((TEnum ({ekind = ik; _ }, _)) as t, e), `Int c -> (* Can only meet the t part of an Lval in e with c (unless we meet with all overflow possibilities)! Since there is no good way to do this, we only continue if e has no values outside of t. *) (match eval e st with @@ -1794,7 +1801,6 @@ struct | x -> fallback ("CastE: e did evaluate to `Int, but the type did not match" ^ sprint d_type t) st else fallback ("CastE: " ^ sprint d_plainexp e ^ " evaluates to " ^ sprint ID.pretty i ^ " which is bigger than the type it is cast to which is " ^ sprint d_type t) st - | `Float f -> inv_exp (`Float (FD.of_int c)) e st | v -> fallback ("CastE: e did not evaluate to `Int, but " ^ sprint VD.pretty v) st) | e, _ -> fallback (sprint d_plainexp e ^ " not implemented") st in diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 3f41a59554..3e958d0d07 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -1,6 +1,7 @@ open Pretty open PrecisionUtil -open Round +open FloatOps +open Cil module type FloatArith = sig type t @@ -35,66 +36,49 @@ module type FloatDomainBase = sig include Lattice.S include FloatArith with type t := t + val to_int : Cil.ikind -> t -> IntDomain.IntDomTuple.t + val of_const : float -> t - val of_interval : float*float -> t + val of_interval : float * float -> t + val of_string : string -> t + val of_int: IntDomain.IntDomTuple.t -> t + val ending : float -> t val starting : float -> t - val to_float : t -> float option - val maximal : t -> float option - val minimal : t -> float option + val minimal: t -> float option + val maximal: t -> float option - val of_int : IntDomain.IntDomTuple.t -> t - val cast_to : Cil.ikind -> t -> IntDomain.IntDomTuple.t + val is_exact : t -> bool end -module FloatInterval = struct + +module FloatIntervalImpl(Float_t : CFloatType) = struct include Printable.Std (* for default invariant, tag and relift *) - type t = (float * float) option [@@deriving eq, ord, to_yojson] + type t = (Float_t.t * Float_t.t) option [@@deriving eq, ord, to_yojson] - let show = function - | None -> "Float[Top]" - | Some (low, high) -> Printf.sprintf "Float [%.30f,%.30f]" low high - - let big_int_of_float f = - let x, n = Float.frexp f in - let shift = min 52 n in - let x' = x *. Float.pow 2. (Float.of_int shift) in - Big_int_Z.mult_big_int - (Big_int_Z.big_int_of_int64 (Int64.of_float x')) - (Big_int_Z.power_int_positive_int 2 (n - shift)) - - let cast_to ik = function + let hash = Hashtbl.hash + + let to_int ik = function | None -> IntDomain.IntDomTuple.top_of ik | Some (l, h) -> (* as converting from float to integer is (exactly) defined as leaving out the fractional part, (value is truncated towrad zero) we do not require specific rounding here *) - IntDomain.IntDomTuple.of_interval ik (big_int_of_float l, big_int_of_float h) - - let of_int x = match IntDomain.IntDomTuple.minimal x, IntDomain.IntDomTuple.maximal x with - | Some l, Some h when l >= big_int_of_float (-. Float.max_float) && h <= big_int_of_float Float.max_float -> - let l' = Big_int_Z.float_of_big_int l in - let l'' = if big_int_of_float (l') <= l then l' else Float.pred l' in - let h' = Big_int_Z.float_of_big_int h in - let h'' = if big_int_of_float (h') >= h then h' else Float.succ h' in - if l'' = Float.neg_infinity || h'' = Float.infinity then + IntDomain.IntDomTuple.of_interval ik (Float_t.to_big_int l, Float_t.to_big_int h) + + let of_int x = + match IntDomain.IntDomTuple.minimal x, IntDomain.IntDomTuple.maximal x with + | Some l, Some h when l >= Float_t.to_big_int Float_t.lower_bound && h <= Float_t.to_big_int Float_t.upper_bound -> + let l' = Float_t.of_float Down (Big_int_Z.float_of_big_int l) in + let h' = Float_t.of_float Up (Big_int_Z.float_of_big_int h) in + if not (Float_t.is_finite l' && Float_t.is_finite h') then None else - Some (l'', h'') + Some (l', h') | _, _ -> None - let maximal = function - | Some (_, h) -> Some h - | _ -> None - - let minimal = function - | Some (l, _) -> Some l - | _ -> None - - let to_float = function - | Some (l, h) when l = h -> Some l - | _ -> None - - let hash = Hashtbl.hash + let show = function + | None -> Float_t.name ^ ": [Top]" + | Some (low, high) -> Printf.sprintf "%s:[%s,%s]" Float_t.name (Float_t.to_string low) (Float_t.to_string high) let pretty () x = text (show x) @@ -115,12 +99,12 @@ module FloatInterval = struct let is_top = Option.is_none - let neg = Option.map (fun (low, high) -> (-.high, -.low)) + let neg = Option.map (fun (low, high) -> (Float_t.neg high, Float_t.neg low)) let norm v = let normed = match v with | Some (low, high) -> - if Float.is_finite low && Float.is_finite high then + if Float_t.is_finite low && Float_t.is_finite high then if low > high then failwith "invalid Interval" else v else None @@ -134,19 +118,29 @@ module FloatInterval = struct let norm_arb v = match v with | Some (f1, f2) -> - if Float.is_finite f1 && Float.is_finite f2 - then Some(min f1 f2, max f1 f2) + let f1' = Float_t.of_float Nearest f1 in + let f2' = Float_t.of_float Nearest f2 in + if Float_t.is_finite f1' && Float_t.is_finite f2' + then Some(min f1' f2', max f1' f2') else None | _ -> None - (**for QCheck: should describe how to generate random values and shrink possilbe counter examples *) + (**for QCheck: should describe how to generate random values and shrink possible counter examples *) let arbitrary () = QCheck.map norm_arb (QCheck.option (QCheck.pair QCheck.float QCheck.float)) - let of_interval (l, h) = norm @@ Some (min l h, max l h) - let ending end_value = of_interval (-. max_float, end_value) - let starting start_value = of_interval (start_value, max_float) + let of_interval' interval = norm @@ Some interval + let of_interval (l, h) = of_interval' (Float_t.of_float Down (min l h), Float_t.of_float Up (max l h)) + let of_string s = of_interval' (Float_t.atof Down s, Float_t.atof Up s) let of_const f = of_interval (f, f) + let ending e = of_interval' (Float_t.lower_bound, Float_t.of_float Up e) + let starting s = of_interval' (Float_t.of_float Down s, Float_t.upper_bound) + + let minimal x = Option.bind x (fun (l, _) -> Float_t.to_float l) + let maximal x = Option.bind x (fun (_, h) -> Float_t.to_float h) + + let is_exact = function Some (l, v) -> l = v | _ -> false + let leq v1 v2 = match v1, v2 with | _, None -> true @@ -174,8 +168,8 @@ module FloatInterval = struct | Some (l1, h1), Some (l2, h2) -> (**If we widen and we know that neither interval contains +-inf or nan, it is ok to widen only to +-max_float, because a widening with +-inf/nan will always result in the case above -> Top *) - let low = if l1 <= l2 then l1 else (-.Float.max_float) in - let high = if h1 >= h2 then h1 else Float.max_float in + let low = if l1 <= l2 then l1 else Float_t.lower_bound in + let high = if h1 >= h2 then h1 else Float_t.upper_bound in norm @@ Some (low, high) let narrow v1 v2 = (**TODO: support 'threshold_narrowing' and 'narrow_by_meet' option *) @@ -189,7 +183,7 @@ module FloatInterval = struct | Some v1, Some v2 -> eval_operation v1 v2 | _ -> None - let eval_int_binop eval_operation op1 op2 = + let eval_int_binop eval_operation (op1: t) op2 = let a, b = match (op1, op2) with | Some v1, Some v2 -> eval_operation v1 v2 @@ -199,35 +193,35 @@ module FloatInterval = struct (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) let eval_add (l1, h1) (l2, h2) = - Some (add Down l1 l2, add Up h1 h2) + Some (Float_t.add Down l1 l2, Float_t.add Up h1 h2) let eval_sub (l1, h1) (l2, h2) = - Some (sub Down l1 h2, sub Up h1 l2) + Some (Float_t.sub Down l1 h2, Float_t.sub Up h1 l2) let eval_mul (l1, h1) (l2, h2) = - let mul1u = mul Up l1 l2 in - let mul2u = mul Up l1 h2 in - let mul3u = mul Up h1 l2 in - let mul4u = mul Up h1 h2 in - let mul1d = mul Down l1 l2 in - let mul2d = mul Down l1 h2 in - let mul3d = mul Down h1 l2 in - let mul4d = mul Down h1 h2 in + let mul1u = Float_t.mul Up l1 l2 in + let mul2u = Float_t.mul Up l1 h2 in + let mul3u = Float_t.mul Up h1 l2 in + let mul4u = Float_t.mul Up h1 h2 in + let mul1d = Float_t.mul Down l1 l2 in + let mul2d = Float_t.mul Down l1 h2 in + let mul3d = Float_t.mul Down h1 l2 in + let mul4d = Float_t.mul Down h1 h2 in let high = max (max (max mul1u mul2u) mul3u) mul4u in let low = min (min (min mul1d mul2d) mul3d) mul4d in Some (low, high) let eval_div (l1, h1) (l2, h2) = - if l2 <= 0. && h2 >= 0. then None + if l2 <= Float_t.zero && h2 >= Float_t.zero then None else - let div1u = div Up l1 l2 in - let div2u = div Up l1 h2 in - let div3u = div Up h1 l2 in - let div4u = div Up h1 h2 in - let div1d = div Down l1 l2 in - let div2d = div Down l1 h2 in - let div3d = div Down h1 l2 in - let div4d = div Down h1 h2 in + let div1u = Float_t.div Up l1 l2 in + let div2u = Float_t.div Up l1 h2 in + let div3u = Float_t.div Up h1 l2 in + let div4u = Float_t.div Up h1 h2 in + let div1d = Float_t.div Down l1 l2 in + let div2d = Float_t.div Down l1 h2 in + let div3d = Float_t.div Down h1 l2 in + let div4d = Float_t.div Down h1 h2 in let high = max (max (max div1u div2u) div3u) div4u in let low = min (min (min div1d div2d) div3d) div4d in Some (low, high) @@ -285,16 +279,144 @@ module FloatInterval = struct end +module F64Interval = FloatIntervalImpl(CDouble) +module F32Interval = FloatIntervalImpl(CFloat) + +module type FloatDomain = sig + include Lattice.S + include FloatArith with type t := t + + val to_int : Cil.ikind -> t -> IntDomain.IntDomTuple.t + val cast_to : Cil.fkind -> t -> t + + val of_const : Cil.fkind -> float -> t + val of_interval : Cil.fkind -> float*float -> t + val of_string : Cil.fkind -> string -> t + val of_int: Cil.fkind -> IntDomain.IntDomTuple.t -> t + + val top_of: Cil.fkind -> t + val bot_of: Cil.fkind -> t + + val ending : Cil.fkind -> float -> t + val starting : Cil.fkind -> float -> t + + val minimal: t -> float option + val maximal: t -> float option + + val is_exact : t -> bool + val precision : t -> Cil.fkind +end + +module FloatIntervalImplLifted = struct + include Printable.Std (* for default invariant, tag and relift *) + type t = F32 of F32Interval.t | F64 of F64Interval.t [@@deriving to_yojson, eq, ord, hash] + + module F1 = F32Interval + module F2 = F64Interval + + let lift2 (op32, op64) x y = match x, y with + | F32 a, F32 b -> F32 (op32 a b) + | F64 a, F64 b -> F64 (op64 a b) + | F32 a, F64 b -> failwith ("fkinds float and double are incompatible. Values: " ^ Prelude.Ana.sprint F32Interval.pretty a ^ " and " ^ Prelude.Ana.sprint F64Interval.pretty b) + | F64 a, F32 b -> failwith ("fkinds double and float are incompatible. Values: " ^ Prelude.Ana.sprint F64Interval.pretty a ^ " and " ^ Prelude.Ana.sprint F32Interval.pretty b) + + let lift2_cmp (op32, op64) x y = match x, y with + | F32 a, F32 b -> op32 a b + | F64 a, F64 b -> op64 a b + | F32 a, F64 b -> failwith ("fkinds float and double are incompatible. Values: " ^ Prelude.Ana.sprint F32Interval.pretty a ^ " and " ^ Prelude.Ana.sprint F64Interval.pretty b) + | F64 a, F32 b -> failwith ("fkinds double and float are incompatible. Values: " ^ Prelude.Ana.sprint F64Interval.pretty a ^ " and " ^ Prelude.Ana.sprint F32Interval.pretty b) + + let lift (op32, op64) x = match x with + | F32 a -> F32 (op32 a) + | F64 a -> F64 (op64 a) + + let dispatch (op32, op64) x = match x with + | F32 a -> op32 a + | F64 a -> op64 a + + let dispatch_fkind fkind (op32, op64) = match fkind with + | FFloat -> F32 (op32 ()) + | FDouble -> F64 (op64 ()) + | _ -> + (* this sould never be reached, as we have to check for invalid fkind elsewhere, + however we could instead of crashing also return top_of some fkind to vaid this and nonetheless have no actual information about anything*) + failwith "unsupported fkind" + + let neg = function + | F32 x -> F32 (F1.neg x) + | F64 x -> F64 (F2.neg x) + let add = lift2 (F1.add, F2.add) + let sub = lift2 (F1.sub, F2.sub) + let mul = lift2 (F1.mul, F2.mul) + let div = lift2 (F1.div, F2.div) + let lt = lift2_cmp (F1.lt, F2.lt) + let gt = lift2_cmp (F1.gt, F2.gt) + let le = lift2_cmp (F1.le, F2.le) + let ge = lift2_cmp (F1.ge, F2.ge) + let eq = lift2_cmp (F1.eq, F2.eq) + let ne = lift2_cmp (F1.ne, F2.ne) + + let bot_of fkind = dispatch_fkind fkind (F1.bot, F2.bot) + let bot () = failwith "bot () is not implemented for FloatIntervalImplLifted." + let is_bot = dispatch (F1.is_bot, F2.is_bot) + let top_of fkind = dispatch_fkind fkind (F1.top, F2.top) + let top () = failwith "top () is not implemented for FloatIntervalImplLifted." + let is_top = dispatch (F1.is_bot, F2.is_bot) + + let precision = dispatch ((fun _ -> FFloat), (fun _ -> FDouble)) + + let leq = lift2_cmp (F1.leq, F2.leq) + let join = lift2 (F1.join, F2.join) + let meet = lift2 (F1.meet, F2.meet) + let widen = lift2 (F1.widen, F2.widen) + let narrow = lift2 (F1.narrow, F2.narrow) + let is_exact = dispatch (F1.is_exact, F2.is_exact) + + let show = dispatch (F1.show, F2.show) (* TODO add fkind to output *) + let pretty = (fun () -> dispatch (F1.pretty (), F2.pretty ())) (* TODO add fkind to output *) + + let pretty_diff () (x, y) = lift2_cmp ((fun a b -> F1.pretty_diff () (a, b)), (fun a b -> F2.pretty_diff () (a, b))) x y(* TODO add fkind to output *) + let printXml o = dispatch (F1.printXml o, F2.printXml o) (* TODO add fkind to output *) + + (* This is for debugging *) + let name () = "FloatIntervalImplLifted(F32|F64)" + let to_yojson = dispatch (F1.to_yojson, F2.to_yojson) + let tag = dispatch (F1.tag, F2.tag) + let arbitrary fk = failwith @@ "Arbitrary not implement for " ^ (name ()) ^ "." + + let of_const fkind x = dispatch_fkind fkind ((fun () -> F1.of_const x), (fun () -> F2.of_const x)) + let of_string fkind str = dispatch_fkind fkind ((fun () -> F1.of_string str), (fun () -> F2.of_string str)) + let of_int fkind i = dispatch_fkind fkind ((fun () -> F1.of_int i), (fun () -> F2.of_int i)) + let of_interval fkind i = dispatch_fkind fkind ((fun () -> F1.of_interval i), (fun () -> F2.of_interval i)) + let starting fkind s = dispatch_fkind fkind ((fun () -> F1.starting s), (fun () -> F2.starting s)) + let ending fkind e = dispatch_fkind fkind ((fun () -> F1.ending e), (fun () -> F2.ending e)) + let minimal = dispatch (F1.minimal, F2.minimal) + let maximal = dispatch (F1.maximal, F2.maximal) + let to_int ikind = dispatch (F1.to_int ikind, F2.to_int ikind) + let cast_to fkind x = + let create_interval fkind l h = + match l, h with + | Some l, Some h -> of_interval fkind (l,h) + | Some l, None -> starting fkind l + | None, Some h -> ending fkind h + | _ -> top_of fkind + in + match x, fkind with + | F32 a, FDouble -> create_interval FDouble (F1.minimal a) (F1.maximal a) + | F64 a, FFloat -> create_interval FFloat (F2.minimal a) (F2.maximal a) + | _ -> x +end + module FloatDomTupleImpl = struct include Printable.Std (* for default invariant, tag, ... *) - module F1 = FloatInterval + module F1 = FloatIntervalImplLifted open Batteries type t = F1.t option [@@deriving to_yojson, eq, ord] let name () = "floatdomtuple" - type 'a m = (module FloatDomainBase with type t = 'a) + type 'a m = (module FloatDomain with type t = 'a) (* only first-order polymorphism on functions -> use records to get around monomorphism restriction on arguments (Same trick as used in intDomain) *) type 'b poly_in = { fi : 'a. 'a m -> 'b -> 'a } @@ -314,8 +436,8 @@ module FloatDomTupleImpl = struct let opt_map2 f = curry @@ function Some x, Some y -> Some (f x y) | _ -> None - let exists = function Some true -> true | _ -> false - let for_all = function Some false -> false | _ -> true + let exists = Option.default false + let for_all = Option.default true let mapp r = BatOption.map (r.fp (module F1)) @@ -331,97 +453,103 @@ module FloatDomTupleImpl = struct let show x = Option.map_default identity "" - (mapp { fp= (fun (type a) (module F : FloatDomainBase with type t = a) x -> F.name () ^ ":" ^ F.show x); } x) + (mapp { fp= (fun (type a) (module F : FloatDomain with type t = a) x -> F.name () ^ ":" ^ F.show x); } x) let to_yojson = [%to_yojson: Yojson.Safe.t option] - % mapp { fp= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.to_yojson); } + % mapp { fp= (fun (type a) (module F : FloatDomain with type t = a) -> F.to_yojson); } let hash x = Option.map_default identity 0 - (mapp { fp= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.hash); } x) - - let of_const = - create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.of_const); } - let of_interval = - create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.of_interval); } - let ending = - create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.ending); } - let starting = - create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.starting); } - - let to_float = function - | Some (x) -> F1.to_float x - | _ -> None + (mapp { fp= (fun (type a) (module F : FloatDomain with type t = a) -> F.hash); } x) - let maximal = function - | Some (x) -> F1.maximal x - | _ -> None - let minimal = function - | Some (x) -> F1.minimal x - | _ -> None + let of_const fkind = + create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.of_const fkind); } + + let of_interval fkind = + create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.of_interval fkind); } + let ending fkind = + create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.ending fkind); } + let starting fkind = + create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.starting fkind); } + + let of_string fkind = + create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.of_string fkind); } let top = - create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.top); } + create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.top); } let bot = - create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.bot); } + create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.bot); } + let top_of = + create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.top_of); } + let bot_of = + create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.bot_of); } + let is_bot = exists - % mapp { fp= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.is_bot); } + % mapp { fp= (fun (type a) (module F : FloatDomain with type t = a) -> F.is_bot); } + let is_exact = + exists + % mapp { fp= (fun (type a) (module F : FloatDomain with type t = a) -> F.is_exact); } let is_top = for_all - % mapp { fp= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.is_top); } + % mapp { fp= (fun (type a) (module F : FloatDomain with type t = a) -> F.is_top); } - let of_int = - create { fi= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.of_int); } + let precision = Option.map_default F1.precision FDouble - let cast_to ik x = - match x with - | Some f -> F1.cast_to ik f - | None -> IntDomain.IntDomTuple.top_of ik + let minimal x = Option.bind x F1.minimal + let maximal x = Option.bind x F1.maximal + + let of_int fkind = + create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.of_int fkind); } + + let to_int ik = Option.map_default (F1.to_int ik) (IntDomain.IntDomTuple.top_of ik) + + let cast_to fkind = + map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> (fun x -> F.cast_to fkind x)); } let leq = for_all - %% map2p { f2p= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.leq); } + %% map2p { f2p= (fun (type a) (module F : FloatDomain with type t = a) -> F.leq); } let pretty () x = Option.map_default identity nil - (mapp { fp= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.pretty ()); } x) + (mapp { fp= (fun (type a) (module F : FloatDomain with type t = a) -> F.pretty ()); } x) (* f1: one and only unary op *) let neg = - map { f1= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.neg); } + map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.neg); } (* f2: binary ops *) let join = - map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.join); } + map2 { f2= (fun (type a) (module F : FloatDomain with type t = a) -> F.join); } let meet = - map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.meet); } + map2 { f2= (fun (type a) (module F : FloatDomain with type t = a) -> F.meet); } let widen = - map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.widen); } + map2 { f2= (fun (type a) (module F : FloatDomain with type t = a) -> F.widen); } let narrow = - map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.narrow); } + map2 { f2= (fun (type a) (module F : FloatDomain with type t = a) -> F.narrow); } let add = - map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.add); } + map2 { f2= (fun (type a) (module F : FloatDomain with type t = a) -> F.add); } let sub = - map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.sub); } + map2 { f2= (fun (type a) (module F : FloatDomain with type t = a) -> F.sub); } let mul = - map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.mul); } + map2 { f2= (fun (type a) (module F : FloatDomain with type t = a) -> F.mul); } let div = - map2 { f2= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.div); } + map2 { f2= (fun (type a) (module F : FloatDomain with type t = a) -> F.div); } (* f2: binary ops which return an integer *) let lt = - map2int { f2p= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.lt); } + map2int { f2p= (fun (type a) (module F : FloatDomain with type t = a) -> F.lt); } let gt = - map2int { f2p= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.gt); } + map2int { f2p= (fun (type a) (module F : FloatDomain with type t = a) -> F.gt); } let le = - map2int { f2p= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.le); } + map2int { f2p= (fun (type a) (module F : FloatDomain with type t = a) -> F.le); } let ge = - map2int { f2p= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.ge); } + map2int { f2p= (fun (type a) (module F : FloatDomain with type t = a) -> F.ge); } let eq = - map2int { f2p= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.eq); } + map2int { f2p= (fun (type a) (module F : FloatDomain with type t = a) -> F.eq); } let ne = - map2int { f2p= (fun (type a) (module F : FloatDomainBase with type t = a) -> F.ne); } + map2int { f2p= (fun (type a) (module F : FloatDomain with type t = a) -> F.ne); } let pretty_diff () (x, y) = dprintf "%a instead of %a" pretty x pretty y diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index 13871b0799..8113928110 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -30,52 +30,55 @@ module type FloatArith = sig (** Not equal to: [x != y] *) end -module FloatInterval : sig (**Currently just for FloatDomainTest *) - type t = (float * float) option +module type FloatDomainBase = sig + include Lattice.S include FloatArith with type t := t - include Lattice.S with type t := t - - val top : unit -> t - - val is_bot : t -> bool - val is_top : t -> bool + val to_int : Cil.ikind -> t -> IntDomain.IntDomTuple.t val of_const : float -> t - val of_interval : float*float -> t - val of_int : IntDomain.IntDomTuple.t -> t - val cast_to : Cil.ikind -> t -> IntDomain.IntDomTuple.t + val of_interval : float * float -> t + val of_string : string -> t + val of_int: IntDomain.IntDomTuple.t -> t val ending : float -> t val starting : float -> t - val show : t -> string + val minimal: t -> float option + val maximal: t -> float option - val equal : t -> t -> bool - - val leq : t -> t -> bool - - val arbitrary : unit -> t QCheck.arbitrary + val is_exact : t -> bool end -module type FloatDomainBase = sig +(* Only required for testing *) +module F64Interval : FloatDomainBase +module F32Interval : FloatDomainBase + +module type FloatDomain = sig include Lattice.S include FloatArith with type t := t - val of_const : float -> t - val of_interval : float*float -> t + val to_int : Cil.ikind -> t -> IntDomain.IntDomTuple.t + val cast_to : Cil.fkind -> t -> t - val ending : float -> t - val starting : float -> t + val of_const : Cil.fkind -> float -> t + val of_interval : Cil.fkind -> float*float -> t + val of_string : Cil.fkind -> string -> t + val of_int: Cil.fkind -> IntDomain.IntDomTuple.t -> t + + val top_of: Cil.fkind -> t + val bot_of: Cil.fkind -> t + + val ending : Cil.fkind -> float -> t + val starting : Cil.fkind -> float -> t - val to_float : t -> float option - val maximal : t -> float option - val minimal : t -> float option + val minimal: t -> float option + val maximal: t -> float option - val of_int : IntDomain.IntDomTuple.t -> t - val cast_to : Cil.ikind -> t -> IntDomain.IntDomTuple.t + val is_exact : t -> bool + val precision : t -> Cil.fkind end module FloatDomTupleImpl : sig - include FloatDomainBase + include FloatDomain end diff --git a/src/cdomains/floatOps/floatOps.ml b/src/cdomains/floatOps/floatOps.ml new file mode 100644 index 0000000000..ca82ca97bc --- /dev/null +++ b/src/cdomains/floatOps/floatOps.ml @@ -0,0 +1,104 @@ +type round_mode = + | Nearest + | ToZero + | Up + | Down + +module type CFloatType = sig + type t + + val name: string + val zero: t + val upper_bound: t + val lower_bound: t + val smallest : t + + val of_float: round_mode -> float -> t + val to_float: t -> float option + val to_big_int: t -> Big_int_Z.big_int + + val is_finite: t -> bool + val pred: t -> t + val succ: t -> t + + val equal: t -> t -> bool + val compare: t -> t -> int + val to_yojson: t -> Yojson.Safe.t + val to_string: t -> string + + val neg: t -> t + val add: round_mode -> t -> t -> t + val sub: round_mode -> t -> t -> t + val mul: round_mode -> t -> t -> t + val div: round_mode -> t -> t -> t + val atof: round_mode -> string -> t +end + +let big_int_of_float f = + let x, n = Float.frexp f in + let shift = min 52 n in + let x' = x *. Float.pow 2. (Float.of_int shift) in + Big_int_Z.mult_big_int + (Big_int_Z.big_int_of_int64 (Int64.of_float x')) + (Big_int_Z.power_int_positive_int 2 (n - shift)) + +module CDouble = struct + type t = float [@@deriving eq, ord, to_yojson] + + let name = "double" + let zero = Float.zero + let upper_bound = Float.max_float + let lower_bound = -. Float.max_float + let smallest = Float.min_float + + let of_float _ x = x + let to_float x = Some x + let to_big_int = big_int_of_float + + let is_finite = Float.is_finite + let pred = Float.pred + let succ = Float.succ + + let to_string = Float.to_string + + let neg = Float.neg + external add: round_mode -> t -> t -> t = "add_double" + external sub: round_mode -> t -> t -> t = "sub_double" + external mul: round_mode -> t -> t -> t = "mul_double" + external div: round_mode -> t -> t -> t = "div_double" + + external atof: round_mode -> string -> t = "atof_double" +end + +module CFloat = struct + type t = float [@@deriving eq, ord, to_yojson] + + let name = "float" + let zero = Float.zero + + external upper': unit -> float = "max_float" + external smallest': unit -> float = "smallest_float" + + let upper_bound = upper' () + let lower_bound = -. upper_bound + let smallest = smallest' () + + let to_float x = Some x + let to_big_int = big_int_of_float + + let is_finite x = Float.is_finite x && x >= lower_bound && x <= upper_bound + + let to_string = Float.to_string + + let neg = Float.neg + external add: round_mode -> t -> t -> t = "add_float" + external sub: round_mode -> t -> t -> t = "sub_float" + external mul: round_mode -> t -> t -> t = "mul_float" + external div: round_mode -> t -> t -> t = "div_float" + + external atof: round_mode -> string -> t = "atof_float" + + let of_float mode x = add mode zero x + let pred x = of_float Down (Float.pred x) + let succ x = of_float Up (Float.succ x) +end diff --git a/src/cdomains/floatOps/floatOps.mli b/src/cdomains/floatOps/floatOps.mli new file mode 100644 index 0000000000..4dd497994a --- /dev/null +++ b/src/cdomains/floatOps/floatOps.mli @@ -0,0 +1,40 @@ +type round_mode = + | Nearest + | ToZero + | Up + | Down + +module type CFloatType = sig + type t + + val name: string + val zero: t + val upper_bound: t + val lower_bound: t + val smallest : t + + val of_float: round_mode -> float -> t + val to_float: t -> float option + val to_big_int: t -> Big_int_Z.big_int + + val is_finite: t -> bool + val pred: t -> t + val succ: t -> t + + val equal: t -> t -> bool + val compare: t -> t -> int + val to_yojson: t -> Yojson.Safe.t + val to_string: t -> string + + + val neg: t -> t + val add: round_mode -> t -> t -> t + val sub: round_mode -> t -> t -> t + val mul: round_mode -> t -> t -> t + val div: round_mode -> t -> t -> t + val atof: round_mode -> string -> t +end + +module CDouble : CFloatType +module CFloat : CFloatType + diff --git a/src/cdomains/floatOps/stubs.c b/src/cdomains/floatOps/stubs.c new file mode 100644 index 0000000000..74bbfe030d --- /dev/null +++ b/src/cdomains/floatOps/stubs.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include + +enum round_mode +{ + Nearest, + ToZero, + Up, + Down +}; + +static void change_round_mode(int mode) +{ + switch (mode) + { + case Nearest: + fesetround(FE_TONEAREST); + break; + case ToZero: + fesetround(FE_TOWARDZERO); + break; + case Up: + fesetround(FE_UPWARD); + break; + case Down: + fesetround(FE_DOWNWARD); + break; + default: + assert(0); + break; + } +} + +#define BINARY_OP(name, type, op) \ + CAMLprim value name##_##type(value mode, value x, value y) \ + { \ + change_round_mode(Int_val(mode)); \ + volatile type r, x1 = Double_val(x), y1 = Double_val(y); \ + r = x1 op y1; \ + change_round_mode(Nearest); \ + return caml_copy_double(r); \ + } + +BINARY_OP(add, double, +); +BINARY_OP(add, float, +); +BINARY_OP(sub, double, -); +BINARY_OP(sub, float, -); +BINARY_OP(mul, double, *); +BINARY_OP(mul, float, *); +BINARY_OP(div, double, /); +BINARY_OP(div, float, /); + +CAMLprim value atof_double(value mode, value str) +{ + const char *s = String_val(str); + volatile double r; + change_round_mode(Int_val(mode)); + r = atof(s); + change_round_mode(Nearest); + return caml_copy_double(r); +} + +CAMLprim value atof_float(value mode, value str) +{ + const char *s = String_val(str); + volatile float r; + change_round_mode(Int_val(mode)); + r = (float)atof(s); + change_round_mode(Nearest); + return caml_copy_double(r); +} + + +CAMLprim value max_float(value unit) +{ + return caml_copy_double(FLT_MAX); +} + +CAMLprim value smallest_float(value unit) +{ + return caml_copy_double(FLT_MIN); +} diff --git a/src/cdomains/valueDomain.ml b/src/cdomains/valueDomain.ml index ca84edb488..de76ccc6b0 100644 --- a/src/cdomains/valueDomain.ml +++ b/src/cdomains/valueDomain.ml @@ -148,8 +148,7 @@ struct match t with | t when is_mutex_type t -> `Top | TInt (ik,_) -> `Int (ID.top_of ik) - | TFloat (FDouble, _) -> `Float (FD.top ()) - | TFloat (fk, _) -> `Top (* TODO(Practical2022): extend to other floating point types *) + | TFloat (FFloat | FDouble as fkind, _) -> `Float (FD.top_of fkind) | TPtr _ -> `Address AD.top_ptr | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> init_value fd.ftype) ci) | TComp ({cstruct=false; _},_) -> `Union (Unions.top ()) @@ -165,8 +164,7 @@ struct let rec top_value (t: typ): t = match t with | TInt (ik,_) -> `Int (ID.(cast_to ik (top_of ik))) - | TFloat (FDouble, _) -> `Float (FD.top ()) - | TFloat (fk, _) -> `Top (* TODO(Practical2022): extend to other floating point types *) + | TFloat (FFloat | FDouble as fkind, _) -> `Float (FD.top_of fkind) | TPtr _ -> `Address AD.top_ptr | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> top_value fd.ftype) ci) | TComp ({cstruct=false; _},_) -> `Union (Unions.top ()) @@ -195,7 +193,7 @@ struct let rec zero_init_value (t:typ): t = match t with | TInt (ikind, _) -> `Int (ID.of_int ikind BI.zero) - | TFloat (FDouble, _) -> `Float (FD.of_const 0.0) + | TFloat (FFloat | FDouble as fkind, _) -> `Float (FD.of_const fkind 0.0) | TPtr _ -> `Address AD.null_ptr | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> zero_init_value fd.ftype) ci) | TComp ({cstruct=false; _} as ci,_) -> @@ -373,15 +371,10 @@ struct let log_top (_,l,_,_) = Messages.tracel "cast" "log_top at %d: %a to %a is top!\n" l pretty v d_type t in let t = unrollType t in let v' = match t with - | TFloat (FDouble,_) -> - `Float (match v with - |`Int ix -> (FD.of_int ix) - | _ -> FD.top ()) - | TFloat (fk,_) -> log_top __POS__; `Top (* TODO(Practical2022): extend to other floating point types *) | TInt (ik,_) -> `Int (ID.cast_to ?torg ik (match v with | `Int x -> x - | `Float x -> FD.cast_to ik x + | `Float x -> FD.to_int ik x | `Address x when AD.equal x AD.null_ptr -> ID.of_int (ptr_ikind ()) BI.zero | `Address x when AD.is_not_null x -> ID.of_excl_list (ptr_ikind ()) [BI.zero] (*| `Struct x when Structs.cardinal x > 0 -> @@ -390,6 +383,11 @@ struct (match Structs.get x first with `Int x -> x | _ -> raise CastError)*) | _ -> log_top __POS__; ID.top_of ik )) + | TFloat (FFloat | FDouble as fkind,_) -> + (match v with + |`Int ix -> `Float (FD.of_int fkind ix) + |`Float fx -> `Float (FD.cast_to fkind fx) + | _ -> log_top __POS__; `Top) | TEnum ({ekind=ik; _},_) -> `Int (ID.cast_to ?torg ik (match v with | `Int x -> (* TODO warn if x is not in the constant values of ei.eitems? (which is totally valid (only ik is relevant for wrapping), but might be unintended) *) x diff --git a/src/dune b/src/dune index 38e3708e05..5a6516d409 100644 --- a/src/dune +++ b/src/dune @@ -8,7 +8,7 @@ (public_name goblint.lib) (wrapped false) (modules :standard \ goblint mainarinc mainspec privPrecCompare apronPrecCompare messagesCompare) - (libraries goblint.sites goblint-cil.all-features batteries.unthreaded qcheck-core.runner sha json-data-encoding jsonrpc cpu arg-complete fpath round + (libraries goblint.sites goblint-cil.all-features batteries.unthreaded qcheck-core.runner sha json-data-encoding jsonrpc cpu arg-complete fpath ; Conditionally compile based on whether apron optional dependency is installed or not. ; Alternative dependencies seem like the only way to optionally depend on optional dependencies. ; See: https://dune.readthedocs.io/en/stable/concepts.html#alternative-dependencies. @@ -33,6 +33,7 @@ (-> violationZ3.no-z3.ml) ) ) + (foreign_stubs (language c) (names stubs)) (preprocess (pps ppx_deriving.std ppx_deriving_hash ppx_deriving_yojson ppx_distr_guards ocaml-monadic ppx_blob)) diff --git a/tests/regression/56-floats/01-base.c b/tests/regression/56-floats/01-base.c index 199310d998..0635df2512 100644 --- a/tests/regression/56-floats/01-base.c +++ b/tests/regression/56-floats/01-base.c @@ -6,20 +6,27 @@ int main() { - double middle; - double a = 2.; - double b = 4.; + double x, a = 2., b = 3. + 1; + float y, c = 2.f, d = 3.f + 1; - assert(middle == 2.); // UNKNOWN! + assert(x == 2.); // UNKNOWN! + assert(y == 2.f); // UNKNOWN! assert(a == 2.); // SUCCESS! assert(a < 10.); // SUCCESS! assert(a > 10.); // FAIL! - middle = (a + b) / 2.; // naive way of computing the middle + assert(c == 2.f); // SUCCESS! + assert(c < 10.f); // SUCCESS! + assert(c > 10.f); // FAIL! - assert(middle == 3.); // SUCCESS! + x = (a + b) / 2.; // naive way of computing the middle + y = (c + d) / 2.; // naive way of computing the middle - assert(-97. == middle - 100.); + assert(x == 3.); // SUCCESS! + assert(y == 3.f); // SUCCESS! + + assert(-97. == x - 100.); + assert(-97.f == y - 100.f); return 0; } diff --git a/tests/regression/56-floats/03-infinity_or_nan.c b/tests/regression/56-floats/03-infinity_or_nan.c index ea8e846de9..bd364b168f 100644 --- a/tests/regression/56-floats/03-infinity_or_nan.c +++ b/tests/regression/56-floats/03-infinity_or_nan.c @@ -4,7 +4,11 @@ void main() { double data; - + float data2; + double result1 = data + 1.0; // WARN: Could be +/-infinity or Nan - double result2 = data / 0.; // WARN: Could be +/-infinity or Nan + double result2 = data / 0.; // WARN: Could be +/-infinity or Nan + + double result3 = data2 + 1.0f; // WARN: Could be +/-infinity or Nan + double result4 = data2 / 0.f; // WARN: Could be +/-infinity or Nan } diff --git a/tests/regression/56-floats/04-casts.c b/tests/regression/56-floats/04-casts.c index dfd01c57fb..fe16e1687a 100644 --- a/tests/regression/56-floats/04-casts.c +++ b/tests/regression/56-floats/04-casts.c @@ -13,14 +13,28 @@ int main() { + int rnd; + + double value; + float value2; + int i; + long l; + unsigned u; + // Casts from double into different variants of ints - assert((int)0.0); // FAIL! - assert((long)0.0); // FAIL! - assert((unsigned)0.0); // FAIL! + assert((int)0.0); // FAIL! + assert((long)0.0); // FAIL! + assert((unsigned)0.0); // FAIL! + assert((int)0.0f); // FAIL! + assert((long)0.0f); // FAIL! + assert((unsigned)0.0f); // FAIL! - assert((unsigned)1.0); // SUCCESS! - assert((long)2.0); // SUCCESS! - assert((int)3.0); // SUCCESS! + assert((unsigned)1.0); // SUCCESS! + assert((long)2.0); // SUCCESS! + assert((int)3.0); // SUCCESS! + assert((unsigned)1.0f); // SUCCESS! + assert((long)2.0f); // SUCCESS! + assert((int)3.0f); // SUCCESS! // Cast from int into double assert((double)0); // FAIL! @@ -31,29 +45,40 @@ int main() assert((double)2l); // SUCCESS! assert((double)3); // SUCCESS! - // cast with ranges - int rnd; + assert((float)0); // FAIL! + assert((float)0l); // FAIL! + assert((float)0u); // FAIL! - double value; - int i; - long l; - unsigned u; + assert((float)1u); // SUCCESS! + assert((float)2l); // SUCCESS! + assert((float)3); // SUCCESS! + // cast with ranges RANGE(i, -5, 5); value = (double)i; - assert(-5. <= value && value <= 5.); // SUCCESS! + assert(-5. <= value && value <= 5.f); // SUCCESS! + value2 = (float)i; + assert(-5.f <= value2 && value2 <= 5.); // SUCCESS! RANGE(l, 10, 20); value = l; - assert(10. <= value && value <= 20.); // SUCCESS! + assert(10.f <= value && value <= 20.); // SUCCESS! + value2 = l; + assert(10. <= value2 && value2 <= 20.f); // SUCCESS! RANGE(u, 100, 1000); value = u; assert(value > 1.); // SUCCESS! + value2 = u; + assert(value2 > 1.f); // SUCCESS! - RANGE(value, -10., 10.); + RANGE(value, -10.f, 10.); i = (int)value; assert(-10 <= i && i <= 10); // SUCCESS! + RANGE(value2, -10.f, 10.); + i = (int)value2; + assert(-10 <= i && i <= 10); // SUCCESS! + return 0; } diff --git a/tests/regression/56-floats/05-invariant.c b/tests/regression/56-floats/05-invariant.c index 2c6cae262a..34cf95b226 100644 --- a/tests/regression/56-floats/05-invariant.c +++ b/tests/regression/56-floats/05-invariant.c @@ -5,27 +5,21 @@ int main() { double a; - int b; + float b; + int c, d; // make a definitly finite! - if (b) + if (c) { a = 100.; + b = 100.; } else { a = -100.; + b = -100.; }; - if (b != 1) - { - assert(b != 1); // SUCCESS! - } - else - { - assert(b == 1); // SUCCESS! - } - if (a != 1.) { // this would require a exclusion list etc. @@ -37,52 +31,63 @@ int main() assert(a == 1.); // SUCCESS! } - if ((int)a) - { - assert(a != 0.); // UNKNOWN! - } - if (a) // here a explicit cast to (int) should be inserted + if (b == 1.f) { - assert(a != 0.); // UNKNOWN! + assert(b == 1.f); // SUCCESS! } if (a <= 5.) { assert(a <= 5.); // SUCCESS! } + if (b <= 5.f) + { + assert(b <= 5.f); // SUCCESS! + } if (a <= 5. && a >= -5.) { assert(a <= 5. && a >= -5.); // SUCCESS! } + if (b <= 5.f && b >= -5.f) + { + assert(b <= 5. && b >= -5.); // SUCCESS! + } - if (a + 5. < 10.) + if (a + 5.f < 10.f) { assert(a < 5.); // SUCCESS! } + if (b + 5.f < 10.f) + { + assert(b < 5.f); // SUCCESS! + } - if (a * 2. < 6.) + if (a * 2. < 6.f) { assert(a < 3.); // SUCCESS! } + if (b * 2.f < 6.f) + { + assert(b < 3.f); // SUCCESS! + } if (a / 3. > 10.) { assert(a > 30.); // SUCCESS! } + if (b / 3.f > 10.f) + { + assert(b > 30.); // SUCCESS! + } - if (((int)a) < 10) + if (a < 10) { assert(a < 10.); // SUCCESS! } - - int c; - - if (0.5 < (double)c) + if (b < 10) { - assert(0 < c); // SUCCESS! - assert(1 < c); // UNKNOWN! - assert(1 <= c); // SUCCESS! + assert(b < 10.f); // SUCCESS! } if (a > 1.) diff --git a/unittest/cdomains/floatDomainTest.ml b/unittest/cdomains/floatDomainTest.ml index c04ae53fc0..bc0871e813 100644 --- a/unittest/cdomains/floatDomainTest.ml +++ b/unittest/cdomains/floatDomainTest.ml @@ -1,32 +1,43 @@ open OUnit2 -open Round - -module FloatInterval = +open FloatOps + +module FloatInterval(Float_t: CFloatType)(Domain_t: FloatDomain.FloatDomainBase) = struct - module FI = FloatDomain.FloatInterval + module FI = Domain_t module IT = IntDomain.IntDomTuple - - let fmax = Float.max_float - let fmin = -. Float.max_float - let fsmall = Float.min_float - + + let to_float = Float_t.to_float + let of_float = Float_t.of_float + let add = Float_t.add + let sub = Float_t.sub + let mul = Float_t.mul + let div = Float_t.div + + let pred x = Option.get (to_float (Float_t.pred (of_float Nearest x))) + let succ x = Option.get (to_float (Float_t.succ (of_float Nearest x))) + + let fmax = Option.get (to_float Float_t.upper_bound) + let fmin = Option.get (to_float Float_t.lower_bound) + let fsmall = Option.get (to_float Float_t.smallest) + let fi_zero = FI.of_const 0. let fi_one = FI.of_const 1. let fi_neg_one = FI.of_const (-.1.) let itb_true = IT.of_int IBool (Big_int_Z.big_int_of_int 1) let itb_false = IT.of_int IBool (Big_int_Z.big_int_of_int 0) let itb_unknown = IT.of_interval IBool (Big_int_Z.big_int_of_int 0, Big_int_Z.big_int_of_int 1) - + let assert_equal v1 v2 = assert_equal ~cmp:FI.equal ~printer:FI.show v1 v2 - + let itb_xor v1 v2 = (**returns true, if both IntDomainTuple bool results are either unknown or different *) ((IT.equal v1 itb_unknown) && (IT.equal v2 itb_unknown)) || ((IT.equal v1 itb_true) && (IT.equal v2 itb_false)) || ((IT.equal v1 itb_false) && (IT.equal v2 itb_true)) - + (**interval tests *) let test_FI_nan _ = assert_equal (FI.top ()) (FI.of_const Float.nan) - + + let test_FI_add_specific _ = let (+) = FI.add in let (=) a b = assert_equal b a in @@ -34,65 +45,69 @@ struct (FI.of_const (-. 0.)) = fi_zero; fi_zero + fi_one = fi_one; fi_neg_one + fi_one = fi_zero; - fi_one + (FI.of_const fmax) = None; - fi_neg_one + (FI.of_const fmin) = None; - fi_neg_one + (FI.of_const fmax) = (FI.of_interval ((Float.pred fmax), fmax)); - fi_one + (FI.of_const fmin) = (FI.of_interval (fmin, Float.succ fmin)); - FI.top () + FI.top () = None; + fi_one + (FI.of_const fmax) = FI.top (); + fi_neg_one + (FI.of_const fmin) = FI.top (); + fi_neg_one + (FI.of_const fmax) = (FI.of_interval ((pred fmax), fmax)); + fi_one + (FI.of_const fmin) = (FI.of_interval (fmin, succ fmin)); + FI.top () + FI.top () = FI.top (); (FI.of_const fmin) + (FI.of_const fmax) = fi_zero; (FI.of_const fsmall) + (FI.of_const fsmall) = FI.of_const (fsmall +. fsmall); - (FI.of_const fsmall) + (FI.of_const 1.) = FI.of_interval (1., Float.succ (1. +. fsmall)); + (FI.of_const fsmall) + (FI.of_const 1.) = FI.of_interval (1., succ (1. +. fsmall)); (FI.of_interval (1., 2.)) + (FI.of_interval (2., 3.)) = FI.of_interval (3., 5.); (FI.of_interval (-. 2., 3.)) + (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 102., 23.); end - + let test_FI_sub_specific _ = let (-) = FI.sub in let (=) a b = assert_equal b a in begin fi_zero - fi_one = fi_neg_one; fi_neg_one - fi_one = FI.of_const (-. 2.); - fi_one - (FI.of_const fmin) = None; - fi_neg_one - (FI.of_const fmax) = None; - (FI.of_const fmax) - fi_one = (FI.of_interval ((Float.pred fmax), fmax)); - (FI.of_const fmin) - fi_neg_one = (FI.of_interval (fmin, Float.succ fmin)); - FI.top () - FI.top () = None; + fi_one - (FI.of_const fmin) = FI.top (); + fi_neg_one - (FI.of_const fmax) = FI.top (); + (FI.of_const fmax) - fi_one = (FI.of_interval ((pred fmax), fmax)); + (FI.of_const fmin) - fi_neg_one = (FI.of_interval (fmin, succ fmin)); + FI.top () - FI.top () = FI.top (); (FI.of_const fmax) - (FI.of_const fmax) = fi_zero; (FI.of_const fsmall) - (FI.of_const fsmall) = fi_zero; - (FI.of_const fsmall) - (FI.of_const 1.) = FI.of_interval (-. 1., Float.succ (-. 1.)); + (FI.of_const fsmall) - (FI.of_const 1.) = FI.of_interval (-. 1., succ (-. 1.)); (FI.of_interval (-. 2., 3.)) - (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 22., 103.); (FI.of_const (-. 0.)) - fi_zero = fi_zero end - + let test_FI_mul_specific _ = let ( * ) = FI.mul in let (=) a b = assert_equal b a in begin fi_zero * fi_one = fi_zero; - (FI.of_const 2.) * (FI.of_const fmin) = None; - (FI.of_const 2.) * (FI.of_const fmax) = None; + (FI.of_const 2.) * (FI.of_const fmin) = FI.top (); + (FI.of_const 2.) * (FI.of_const fmax) = FI.top (); (FI.of_const fsmall) * (FI.of_const fmax) = FI.of_const (fsmall *. fmax); - FI.top () * FI.top () = None; + FI.top () * FI.top () = FI.top (); (FI.of_const fmax) * fi_zero = fi_zero; (FI.of_const fsmall) * fi_zero = fi_zero; (FI.of_const fsmall) * fi_one = FI.of_const fsmall; (FI.of_const fmax) * fi_one = FI.of_const fmax; (FI.of_const 2.) * (FI.of_const 0.5) = fi_one; (FI.of_interval (-. 2., 3.)) * (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 300., 200.); - (FI.of_const 1.00000000000000111) * (FI.of_const 1.00000000000000111) = FI.of_interval (1.00000000000000222 , Float.succ 1.00000000000000222); - (FI.of_const (-. 1.00000000000000111)) * (FI.of_const 1.00000000000000111) = FI.of_interval (Float.pred (-. 1.00000000000000222), -. 1.00000000000000222) + + let up = if Float_t.name <> "float" then succ 1.00000000000000222 else succ (succ 1.00000000000000111 *. succ 1.00000000000000111) in + begin + (FI.of_const 1.00000000000000111) * (FI.of_const 1.00000000000000111) = FI.of_interval (1.00000000000000222 , up); + (FI.of_const (-. 1.00000000000000111)) * (FI.of_const 1.00000000000000111) = FI.of_interval (-. up, -. 1.00000000000000222) + end end - + let test_FI_div_specific _ = let (/) = FI.div in let (=) a b = assert_equal b a in begin fi_zero / fi_one = fi_zero; - (FI.of_const 2.) / fi_zero = None; - fi_zero / fi_zero = None; - (FI.of_const fmax) / (FI.of_const fsmall) = None; - (FI.of_const fmin) / (FI.of_const fsmall) = None; - FI.top () / FI.top () = None; + (FI.of_const 2.) / fi_zero = FI.top (); + fi_zero / fi_zero = FI.top (); + (FI.of_const fmax) / (FI.of_const fsmall) = FI.top (); + (FI.of_const fmin) / (FI.of_const fsmall) = FI.top (); + FI.top () / FI.top () = FI.top (); fi_zero / fi_one = fi_zero; (FI.of_const fsmall) / fi_one = FI.of_const fsmall; (FI.of_const fsmall) / (FI.of_const fsmall) = fi_one; @@ -100,14 +115,13 @@ struct (FI.of_const fmax) / fi_one = FI.of_const fmax; (FI.of_const 2.) / (FI.of_const 0.5) = (FI.of_const 4.); (FI.of_const 4.) / (FI.of_const 2.) = (FI.of_const 2.); - (FI.of_interval (-. 2., 3.)) / (FI.of_interval (-. 100., 20.)) = None; + (FI.of_interval (-. 2., 3.)) / (FI.of_interval (-. 100., 20.)) = FI.top (); (FI.of_interval (6., 10.)) / (FI.of_interval (2., 3.)) = (FI.of_interval (2., 5.)); - - (FI.of_const 1.00000000000000111) / (FI.of_const 1.00000000000000111) = fi_one; - (FI.of_const 1.) / (FI.of_const 3.) = (FI.of_interval (Float.pred 0.333333333333333370340767487505, 0.333333333333333370340767487505)); - (FI.of_const (-. 1.)) / (FI.of_const 3.) = (FI.of_interval (-. 0.333333333333333370340767487505, Float.succ (-. 0.333333333333333370340767487505))) + + (FI.of_const 1.) / (FI.of_const 3.) = (FI.of_interval (pred 0.333333333333333370340767487505, 0.333333333333333370340767487505)); + (FI.of_const (-. 1.)) / (FI.of_const 3.) = (FI.of_interval (-. 0.333333333333333370340767487505, succ (-. 0.333333333333333370340767487505))) end - + let test_FI_casti2f_specific _ = let cast_bool a b = assert_equal b (FI.of_int (IT.of_int IBool (Big_int_Z.big_int_of_int a))) in @@ -128,7 +142,7 @@ struct cast (IT.of_interval IInt (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)) (FI.of_interval (-. 100., 100.)); cast (IT.of_interval IUShort (Big_int_Z.big_int_of_int 2, Big_int_Z.big_int_of_int 100)) (FI.of_interval (2., 100.)); cast (IT.of_interval IShort (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)) (FI.of_interval (-. 100., 100.)); - + cast (IT.of_interval IULong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_int64 Int64.max_int)) (FI.of_interval (0., 9223372036854775807.)); cast (IT.of_interval IULong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_int64 (9223372036854775806L))) (FI.of_interval (0., 9223372036854775807.)); cast (IT.of_interval ILong (Big_int_Z.big_int_of_int64 Int64.min_int, Big_int_Z.zero_big_int)) (FI.of_interval (-. 9223372036854775808., 0.)); @@ -139,10 +153,10 @@ struct cast (IT.of_interval ILongLong (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)) (FI.of_interval (-. 100., 100.)); GobConfig.set_bool "ana.int.interval" false; end - + let test_FI_castf2i_specific _ = let cast ikind a b = - OUnit2.assert_equal ~cmp:IT.equal ~printer:IT.show b (FI.cast_to ikind a) in + OUnit2.assert_equal ~cmp:IT.equal ~printer:IT.show b (FI.to_int ikind a) in begin GobConfig.set_bool "ana.int.interval" true; cast IInt (FI.of_interval (-2147483648.,2147483647.)) (IT.top_of IInt); @@ -152,14 +166,14 @@ struct cast IBool (FI.of_interval (-9999999999.,9999999999.)) (IT.top_of IBool); cast IBool fi_one (IT.of_bool IBool true); cast IBool fi_zero (IT.of_bool IBool false); - + cast IUChar (FI.of_interval (0.123, 128.999)) (IT.of_interval IUChar (Big_int_Z.big_int_of_int 0, Big_int_Z.big_int_of_int 128)); cast IChar (FI.of_interval (-. 8.0000000, 127.)) (IT.of_interval IChar (Big_int_Z.big_int_of_int (-8), Big_int_Z.big_int_of_int 127)); cast IUInt (FI.of_interval (2., 100.)) (IT.of_interval IUInt (Big_int_Z.big_int_of_int 2, Big_int_Z.big_int_of_int 100)); cast IInt (FI.of_interval (-. 100.2, 100.1)) (IT.of_interval IInt (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); cast IUShort (FI.of_interval (2., 100.)) (IT.of_interval IUShort (Big_int_Z.big_int_of_int 2, Big_int_Z.big_int_of_int 100)); cast IShort (FI.of_interval (-. 100., 100.)) (IT.of_interval IShort (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); - + cast IULong (FI.of_interval (0., 9223372036854775808.)) (IT.of_interval IULong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_string "9223372036854775808")); cast ILong (FI.of_interval (-. 9223372036854775808., 0.)) (IT.of_interval ILong (Big_int_Z.big_int_of_string "-9223372036854775808", Big_int_Z.zero_big_int)); cast ILong (FI.of_interval (-. 100.99999, 100.99999)) (IT.of_interval ILong (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); @@ -168,7 +182,7 @@ struct cast ILongLong (FI.of_interval (-. 100., 100.)) (IT.of_interval ILongLong (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); GobConfig.set_bool "ana.int.interval" false; end - + let test_FI_meet_specific _ = let check_meet a b c = assert_equal c (FI.meet a b) in @@ -178,7 +192,7 @@ struct assert_raises (Failure "meet results in empty Interval") (fun () -> (FI.meet fi_zero fi_one)); check_meet (FI.of_interval (0., 10.)) (FI.of_interval (5., 20.)) (FI.of_interval (5., 10.)); end - + let test_FI_join_specific _ = let check_join a b c = assert_equal c (FI.join a b) in @@ -187,7 +201,7 @@ struct check_join (FI.top ()) fi_one (FI.top ()); check_join (FI.of_interval (0., 10.)) (FI.of_interval (5., 20.)) (FI.of_interval (0., 20.)); end - + let test_FI_leq_specific _ = let check_leq flag a b = OUnit2.assert_equal flag (FI.leq a b) in @@ -200,7 +214,7 @@ struct check_leq true (FI.of_interval (1., 19.)) (FI.of_interval (0., 20.)); check_leq false (FI.of_interval (0., 20.)) (FI.of_interval (20.0001, 20.0002)); end - + let test_FI_widen_specific _ = let check_widen a b c = assert_equal c (FI.widen a b) in @@ -208,11 +222,11 @@ struct check_widen (FI.top ()) (FI.top ()) (FI.top ()); check_widen fi_zero (FI.top ()) (FI.top ()); check_widen (FI.top ()) fi_one (FI.top ()); - check_widen fi_zero fi_one (FI.of_interval (0., Float.max_float)); - check_widen fi_one fi_zero (FI.of_interval (-. Float.max_float, 1.)); - check_widen fi_one (FI.of_interval (0., 2.)) (FI.of_interval (-. Float.max_float, Float.max_float)); + check_widen fi_zero fi_one (FI.of_interval (0., fmax)); + check_widen fi_one fi_zero (FI.of_interval (fmin, 1.)); + check_widen fi_one (FI.of_interval (0., 2.)) (FI.of_interval (fmin, fmax)); end - + let test_FI_narrow_specific _ = let check_narrow a b c = assert_equal c (FI.narrow a b) in @@ -222,58 +236,64 @@ struct check_narrow (FI.top ()) fi_zero fi_zero; check_narrow fi_zero fi_one fi_zero; end - + (**TODO: add tests for specific edge cases (eg. overflow to infinity when dbl.max + 1) *) - + (**interval tests using QCheck arbitraries *) let test_FI_not_bot = QCheck.Test.make ~name:"test_FI_not_bot" (FI.arbitrary ()) (fun arg -> not (FI.is_bot arg)) - + let test_FI_of_const_not_bot = QCheck.Test.make ~name:"test_FI_of_const_not_bot" QCheck.float (fun arg -> not (FI.is_bot (FI.of_const arg))) - + let test_FI_div_zero_result_top = QCheck.Test.make ~name:"test_FI_div_zero_result_top" (FI.arbitrary ()) (fun arg -> FI.is_top (FI.div arg (FI.of_const 0.))) - + let test_FI_accurate_neg = QCheck.Test.make ~name:"test_FI_accurate_neg" QCheck.float (fun arg -> FI.equal (FI.of_const (-.arg)) (FI.neg (FI.of_const arg))) - + let test_FI_lt_xor_ge = QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> itb_xor (FI.lt arg1 arg2) (FI.ge arg1 arg2)) - + let test_FI_gt_xor_le = QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> itb_xor (FI.gt arg1 arg2) (FI.le arg1 arg2)) - + let test_FI_eq_xor_ne = QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> itb_xor (FI.eq arg1 arg2) (FI.ne arg1 arg2)) - + let test_FI_add = QCheck.Test.make ~name:"test_FI_add" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.add (FI.of_const arg1) (FI.of_const arg2) in - (FI.leq (FI.of_const (add Up arg1 arg2)) result) && (FI.leq (FI.of_const (add Down arg1 arg2)) result)) - + (FI.leq (FI.of_const (Option.get (to_float (add Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && + (FI.leq (FI.of_const (Option.get (to_float (add Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) + let test_FI_sub = QCheck.Test.make ~name:"test_FI_sub" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.sub (FI.of_const arg1) (FI.of_const arg2) in - (FI.leq (FI.of_const (sub Up arg1 arg2)) result) && (FI.leq (FI.of_const (sub Down arg1 arg2)) result)) - + (FI.leq (FI.of_const (Option.get (to_float (sub Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && + (FI.leq (FI.of_const (Option.get (to_float (sub Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) + let test_FI_mul = QCheck.Test.make ~name:"test_FI_mul" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.mul (FI.of_const arg1) (FI.of_const arg2) in - (FI.leq (FI.of_const (mul Up arg1 arg2)) result) && (FI.leq (FI.of_const (mul Down arg1 arg2)) result)) - + (FI.leq (FI.of_const (Option.get (to_float (mul Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && + (FI.leq (FI.of_const (Option.get (to_float (mul Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) + + let test_FI_div = QCheck.Test.make ~name:"test_FI_div" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.div (FI.of_const arg1) (FI.of_const arg2) in - (FI.leq (FI.of_const (div Up arg1 arg2)) result) && (FI.leq (FI.of_const (div Down arg1 arg2)) result)) - + (FI.leq (FI.of_const (Option.get (to_float (div Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && + (FI.leq (FI.of_const (Option.get (to_float (div Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) + + let test () = [ "test_FI_nan" >:: test_FI_nan; "test_FI_add_specific" >:: test_FI_add_specific; @@ -289,7 +309,7 @@ struct "test_FI_widen_specific" >:: test_FI_widen_specific; "test_FI_narrow_specific" >:: test_FI_narrow_specific; ] - + let test_qcheck () = QCheck_ounit.to_ounit2_test_list [ test_FI_not_bot; test_FI_of_const_not_bot; @@ -304,10 +324,16 @@ struct test_FI_div; ] end - + +module FloatIntervalTest32 = FloatInterval(CFloat)(FloatDomain.F32Interval) +module FloatIntervalTest64 = FloatInterval(CDouble)(FloatDomain.F64Interval) + let test () = "floatDomainTest" >::: [ - "float_interval" >::: FloatInterval.test (); - "float_interval_qcheck" >::: FloatInterval.test_qcheck (); + "float_interval32" >::: FloatIntervalTest32.test (); + "float_interval_qcheck32" >::: FloatIntervalTest32.test_qcheck (); + "float_interval64" >::: FloatIntervalTest64.test (); + "float_interval_qcheck64" >::: FloatIntervalTest64.test_qcheck (); ] + From 82c0bf430e5f96b6cce00154c9f95f9691e36fa9 Mon Sep 17 00:00:00 2001 From: Felix <91671586+FelixKrayer@users.noreply.github.com> Date: Thu, 16 Jun 2022 12:12:24 +0200 Subject: [PATCH 18/54] implemented float classification functions from math.h (#14) --- src/analyses/base.ml | 25 ++++++ src/cdomains/floatDomain.ml | 77 +++++++++++++++++-- src/cdomains/floatDomain.mli | 12 +++ .../56-floats/06-library_functions.c | 51 ++++++++++++ 4 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 tests/regression/56-floats/06-library_functions.c diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 15756b34ea..ed5d781402 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -2398,6 +2398,31 @@ struct | Some x -> assign ctx x (List.hd args) | None -> ctx.local end + (**Floating point classification and unary trigonometric functions defined in c99*) + | `Unknown (("__builtin_isfinite" | "__builtin_isinf" | "__builtin_isinf_sign" | "__builtin_isnan" | "__builtin_isnormal" | "__builtin_signbit") as name) -> + begin match args with + | [x] -> + let eval_x = eval_rv (Analyses.ask_of_ctx ctx) gs st x in + begin match eval_x with + | `Float float_x -> + let result = + begin match name with + | "__builtin_isfinite" -> `Int (ID.cast_to IInt (FD.isfinite float_x)) + | "__builtin_isinf" | "__builtin_isinf_sign" -> `Int (ID.cast_to IInt (FD.isinf float_x)) + | "__builtin_isnan" -> `Int (ID.cast_to IInt (FD.isnan float_x)) + | "__builtin_isnormal" -> `Int (ID.cast_to IInt (FD.isnormal float_x)) + | "__builtin_signbit" -> `Int (ID.cast_to IInt (FD.signbit float_x)) + | _ -> failwith "impossible matching" + end + in + begin match lv with + | Some lv_val -> set ~ctx:(Some ctx) (Analyses.ask_of_ctx ctx) gs st (eval_lv (Analyses.ask_of_ctx ctx) ctx.global st lv_val) (Cilfacade.typeOfLval lv_val) result + | None -> st + end + | _ -> failwith ("non-floating-point argument in call to function "^name) + end + | _ -> failwith ("strange "^name^" arguments") + end (* handling thread creations *) | `ThreadCreate _ -> invalidate_ret_lv ctx.local (* actual results joined via threadspawn *) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 3e958d0d07..1e22e8d9ec 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -30,6 +30,18 @@ module type FloatArith = sig (** Equal to: [x == y] *) val ne : t -> t -> IntDomain.IntDomTuple.t (** Not equal to: [x != y] *) + + (** {unary functions returning int} *) + val isfinite : t -> IntDomain.IntDomTuple.t + (** __builtin_isfinite(x) *) + val isinf : t -> IntDomain.IntDomTuple.t + (** __builtin_isinf(x) *) + val isnan : t -> IntDomain.IntDomTuple.t + (** __builtin_isnan(x) *) + val isnormal : t -> IntDomain.IntDomTuple.t + (** __builtin_isnormal(x) *) + val signbit : t -> IntDomain.IntDomTuple.t + (** __builtin_signbit(x) *) end module type FloatDomainBase = sig @@ -277,6 +289,42 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let ne = eval_int_binop eval_ne + let true_nonZero_IInt = IntDomain.IntDomTuple.of_excl_list IInt [(Big_int_Z.big_int_of_int 0)] + let false_zero_IInt = IntDomain.IntDomTuple.of_int IInt (Big_int_Z.big_int_of_int 0) + let unknown_IInt = IntDomain.IntDomTuple.top_of IInt + + let isfinite op = + match op with + | Some v -> true_nonZero_IInt + | None -> unknown_IInt + + let isinf op = + match op with + | Some v -> false_zero_IInt + | None -> unknown_IInt + + let isnan = isinf (**currently we cannot decide if we are NaN or +-inf; both are only in Top*) + + let isnormal op = + match op with + | Some (l, h) -> + if l >= Float_t.smallest || h <= (Float_t.neg (Float_t.smallest)) then + true_nonZero_IInt + else if l > (Float_t.neg (Float_t.smallest)) && h < Float_t.smallest then + false_zero_IInt + else + unknown_IInt + | None -> unknown_IInt + + (**it seems strange not to return a explicit 1 for negative numbers, but in c99 signbit is defined as: *) + (**<> *) + let signbit op = + match op with + | Some (_, h) when h < Float_t.zero -> true_nonZero_IInt + | Some (l, _) when l > Float_t.zero -> false_zero_IInt + | Some _ -> unknown_IInt (**any interval containing zero has to fall in this case, because we do not distinguish between 0. and -0. *) + | None -> unknown_IInt + end module F64Interval = FloatIntervalImpl(CDouble) @@ -342,9 +390,7 @@ module FloatIntervalImplLifted = struct however we could instead of crashing also return top_of some fkind to vaid this and nonetheless have no actual information about anything*) failwith "unsupported fkind" - let neg = function - | F32 x -> F32 (F1.neg x) - | F64 x -> F64 (F2.neg x) + let neg = lift (F1.neg, F2.neg) let add = lift2 (F1.add, F2.add) let sub = lift2 (F1.sub, F2.sub) let mul = lift2 (F1.mul, F2.mul) @@ -355,6 +401,11 @@ module FloatIntervalImplLifted = struct let ge = lift2_cmp (F1.ge, F2.ge) let eq = lift2_cmp (F1.eq, F2.eq) let ne = lift2_cmp (F1.ne, F2.ne) + let isfinite = dispatch (F1.isfinite, F2.isfinite) + let isinf = dispatch (F1.isinf, F2.isinf) + let isnan = dispatch (F1.isnan, F2.isnan) + let isnormal = dispatch (F1.isnormal, F2.isnormal) + let signbit = dispatch (F1.signbit, F2.signbit) let bot_of fkind = dispatch_fkind fkind (F1.bot, F2.bot) let bot () = failwith "bot () is not implemented for FloatIntervalImplLifted." @@ -441,7 +492,7 @@ module FloatDomTupleImpl = struct let mapp r = BatOption.map (r.fp (module F1)) - let map r a = BatOption.(map (r.f1 (module F1)) a) + let map r a = BatOption.map (r.f1 (module F1)) a let map2 r xa ya = opt_map2 (r.f2 (module F1)) xa ya let map2p r xa ya = opt_map2 (r.f2p (module F1)) xa ya @@ -449,6 +500,10 @@ module FloatDomTupleImpl = struct Option.map_default identity (IntDomain.IntDomTuple.top_of IBool) (opt_map2 (r.f2p (module F1)) xa ya) + let map1int r xa = + Option.map_default identity + (IntDomain.IntDomTuple.top_of IInt) (BatOption.map (r.fp (module F1)) xa) + let ( %% ) f g x = f % g x let show x = @@ -537,7 +592,7 @@ module FloatDomTupleImpl = struct let div = map2 { f2= (fun (type a) (module F : FloatDomain with type t = a) -> F.div); } - (* f2: binary ops which return an integer *) + (* f2p: binary ops which return an integer *) let lt = map2int { f2p= (fun (type a) (module F : FloatDomain with type t = a) -> F.lt); } let gt = @@ -551,6 +606,18 @@ module FloatDomTupleImpl = struct let ne = map2int { f2p= (fun (type a) (module F : FloatDomain with type t = a) -> F.ne); } + (* fp: unary functions which return an integer *) + let isfinite = + map1int { fp= (fun (type a) (module F : FloatDomain with type t = a) -> F.isfinite); } + let isinf = + map1int { fp= (fun (type a) (module F : FloatDomain with type t = a) -> F.isinf); } + let isnan = + map1int { fp= (fun (type a) (module F : FloatDomain with type t = a) -> F.isnan); } + let isnormal = + map1int { fp= (fun (type a) (module F : FloatDomain with type t = a) -> F.isnormal); } + let signbit = + map1int { fp= (fun (type a) (module F : FloatDomain with type t = a) -> F.signbit); } + let pretty_diff () (x, y) = dprintf "%a instead of %a" pretty x pretty y let printXml f x = diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index 8113928110..b8a4ec51dd 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -28,6 +28,18 @@ module type FloatArith = sig (** Equal to: [x == y] *) val ne : t -> t -> IntDomain.IntDomTuple.t (** Not equal to: [x != y] *) + + (** {unary functions returning int} *) + val isfinite : t -> IntDomain.IntDomTuple.t + (** __builtin_isfinite(x) *) + val isinf : t -> IntDomain.IntDomTuple.t + (** __builtin_isinf(x) *) + val isnan : t -> IntDomain.IntDomTuple.t + (** __builtin_isnan(x) *) + val isnormal : t -> IntDomain.IntDomTuple.t + (** __builtin_isnormal(x) *) + val signbit : t -> IntDomain.IntDomTuple.t + (** __builtin_signbit(x) *) end module type FloatDomainBase = sig diff --git a/tests/regression/56-floats/06-library_functions.c b/tests/regression/56-floats/06-library_functions.c new file mode 100644 index 0000000000..5d1e58432e --- /dev/null +++ b/tests/regression/56-floats/06-library_functions.c @@ -0,0 +1,51 @@ +// PARAM: --enable ana.float.interval --enable ana.int.interval +#include +#include +#include + +int main() { + double dbl_min = 2.2250738585072014e-308; + double inf = 1. / 0.; + double nan = 0. / 0.; + + //__buitin_isfinite(x): + assert(__builtin_isfinite(1.0)); //SUCCESS! + assert(__builtin_isfinite(inf)); //UNKNOWN + assert(__builtin_isfinite(nan)); //UNKNOWN + + //__buitin_isinf(x): + assert(__builtin_isinf(1.0)); //FAIL! + assert(__builtin_isinf(inf)); //UNKNOWN + assert(__builtin_isinf(nan)); //UNKNOWN + + //__buitin_isinf_sign(x): + assert(__builtin_isinf_sign(1.0)); //FAIL! + assert(__builtin_isinf_sign(inf)); //UNKNOWN + assert(__builtin_isinf_sign(- inf)); //UNKNOWN + assert(__builtin_isinf_sign(nan)); //UNKNOWN + + //__buitin_isnan(x): + assert(__builtin_isnan(1.0)); //FAIL! + assert(__builtin_isnan(inf)); //UNKNOWN + assert(__builtin_isnan(nan)); //UNKNOWN + + //__buitin_isnormal(x): + assert(__builtin_isnormal(dbl_min)); //SUCCESS! + assert(__builtin_isnormal(0.0)); //FAIL! + assert(__builtin_isnormal(dbl_min / 2)); //FAIL! + assert(__builtin_isnormal(inf)); //UNKNOWN + assert(__builtin_isnormal(nan)); //UNKNOWN + + //__buitin_signbit(x): + assert(__builtin_signbit(1.0)); //FAIL! + assert(__builtin_signbit(-1.0)); //SUCCESS! + assert(__builtin_signbit(0.0)); //UNKNOWN + assert(__builtin_signbit(inf)); //UNKNOWN + assert(__builtin_signbit(- inf)); //UNKNOWN + assert(__builtin_signbit(nan)); //UNKNOWN + + //unimplemented math.h function, should not invalidate globals: + cos(0.1); //NOWARN + j0(0.1); //NOWARN + ldexp(0.1, 1); //NOWARN +} From 7d5b14913c15bac776ef367f6f22f9b891b4a316 Mon Sep 17 00:00:00 2001 From: Dominik Berger Date: Thu, 16 Jun 2022 12:18:51 +0200 Subject: [PATCH 19/54] Add further warnings (#11) --- src/cdomains/floatDomain.ml | 23 ++++++++++++++++++++++- tests/regression/56-floats/06-equality.c | 11 +++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/regression/56-floats/06-equality.c diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 1e22e8d9ec..7803a64721 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -192,7 +192,20 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct (** evaluation of the binary operations *) let eval_binop eval_operation op1 op2 = norm @@ match (op1, op2) with - | Some v1, Some v2 -> eval_operation v1 v2 + | Some v1, Some v2 -> + let is_exact (lower, upper) = (lower = upper) in + let is_exact_before = is_exact v1 && is_exact v2 in + let result = eval_operation v1 v2 in + (match result with + | Some (r1, r2) -> + let is_exact_after = is_exact (r1, r2) + in if not is_exact_after && is_exact_before then + Messages.warn + ~category:Messages.Category.FloatMessage + ~tags:[CWE 197; CWE 681; CWE 1339] + "The result of this operation is not exact, even though the inputs were exact."; + result + | _ -> None) | _ -> None let eval_int_binop eval_operation (op1: t) op2 = @@ -260,11 +273,19 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct else (0, 1) let eval_eq (l1, h1) (l2, h2) = + Messages.warn + ~category:Messages.Category.FloatMessage + ~tags:[CWE 1077] + "Equality between `double` is dangerous!"; if h1 < l2 || h2 < l1 then (0, 0) else if h1 = l1 && h2 = l2 && l1 = l2 then (1, 1) else (0, 1) let eval_ne (l1, h1) (l2, h2) = + Messages.warn + ~category:Messages.Category.FloatMessage + ~tags:[CWE 1077] + "Equality/Inequality between `double` is dangerous!"; if h1 < l2 || h2 < l1 then (1, 1) else if h1 = l1 && h2 = l2 && l1 = l2 then (0, 0) else (0, 1) diff --git a/tests/regression/56-floats/06-equality.c b/tests/regression/56-floats/06-equality.c new file mode 100644 index 0000000000..7139388c92 --- /dev/null +++ b/tests/regression/56-floats/06-equality.c @@ -0,0 +1,11 @@ +// PARAM: --enable ana.float.interval --enable warn.float + +void main() +{ + int check1 = (0.2 == 0.2); // WARN + int check2 = (0.2 != 0.3); // WARN + + // Not all integers that are this big are representable in doubles anymore... + double high_value = 179769313486231568384.0; + double x = high_value + 1; // WARN +} From 6fa973a23e296d8ba9dfc82291ad103d311fd647 Mon Sep 17 00:00:00 2001 From: Dominik Berger Date: Thu, 16 Jun 2022 12:20:45 +0200 Subject: [PATCH 20/54] Fix and remove todos for our first "real" PR (#16) --- src/analyses/base.ml | 2 +- src/cdomains/baseDomain.ml | 2 +- src/util/sarifRules.ml | 1 - unittest/cdomains/floatDomainTest.ml | 88 ++++++++++++++-------------- 4 files changed, 45 insertions(+), 48 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index ed5d781402..6f0165a00c 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -599,7 +599,7 @@ struct in ValueDomain.Structs.fold f s (empty, TS.bot (), false) | `Int _ -> (empty, TS.bot (), false) - | `Float _ -> failwith "todo" + | `Float _ -> (empty, TS.bot (), false) | `Thread _ -> (empty, TS.bot (), false) (* TODO: is this right? *) in reachable_from_value (get (Analyses.ask_of_ctx ctx) ctx.global ctx.local adr None) diff --git a/src/cdomains/baseDomain.ml b/src/cdomains/baseDomain.ml index cc94e361dc..1dd984af04 100644 --- a/src/cdomains/baseDomain.ml +++ b/src/cdomains/baseDomain.ml @@ -196,7 +196,7 @@ module DomWithTrivialExpEval (PrivD: Lattice.S) = DomFunctor (PrivD) (struct begin match CPA.find v r.cpa with | `Int i -> ValueDomain.ID.to_int i - | `Float f -> failwith "todo, change actually required here?" + | `Float f -> failwith "unreachable" | _ -> None end | _ -> None diff --git a/src/util/sarifRules.ml b/src/util/sarifRules.ml index f0c8da4eb5..dff04893c3 100644 --- a/src/util/sarifRules.ml +++ b/src/util/sarifRules.ml @@ -57,7 +57,6 @@ let rules = [ helpUri="https://goblint.in.tum.de/home"; longDescription=""; }; - (* TODO: We'll want to add something like this "Integer Overflow" here also for our float! *) { name="Overflow"; ruleId="GO0006"; diff --git a/unittest/cdomains/floatDomainTest.ml b/unittest/cdomains/floatDomainTest.ml index bc0871e813..8a75726933 100644 --- a/unittest/cdomains/floatDomainTest.ml +++ b/unittest/cdomains/floatDomainTest.ml @@ -1,43 +1,43 @@ open OUnit2 open FloatOps - + module FloatInterval(Float_t: CFloatType)(Domain_t: FloatDomain.FloatDomainBase) = struct module FI = Domain_t module IT = IntDomain.IntDomTuple - + let to_float = Float_t.to_float let of_float = Float_t.of_float let add = Float_t.add let sub = Float_t.sub let mul = Float_t.mul let div = Float_t.div - + let pred x = Option.get (to_float (Float_t.pred (of_float Nearest x))) let succ x = Option.get (to_float (Float_t.succ (of_float Nearest x))) - + let fmax = Option.get (to_float Float_t.upper_bound) let fmin = Option.get (to_float Float_t.lower_bound) let fsmall = Option.get (to_float Float_t.smallest) - + let fi_zero = FI.of_const 0. let fi_one = FI.of_const 1. let fi_neg_one = FI.of_const (-.1.) let itb_true = IT.of_int IBool (Big_int_Z.big_int_of_int 1) let itb_false = IT.of_int IBool (Big_int_Z.big_int_of_int 0) let itb_unknown = IT.of_interval IBool (Big_int_Z.big_int_of_int 0, Big_int_Z.big_int_of_int 1) - + let assert_equal v1 v2 = assert_equal ~cmp:FI.equal ~printer:FI.show v1 v2 - + let itb_xor v1 v2 = (**returns true, if both IntDomainTuple bool results are either unknown or different *) ((IT.equal v1 itb_unknown) && (IT.equal v2 itb_unknown)) || ((IT.equal v1 itb_true) && (IT.equal v2 itb_false)) || ((IT.equal v1 itb_false) && (IT.equal v2 itb_true)) - + (**interval tests *) let test_FI_nan _ = assert_equal (FI.top ()) (FI.of_const Float.nan) - - + + let test_FI_add_specific _ = let (+) = FI.add in let (=) a b = assert_equal b a in @@ -56,7 +56,7 @@ struct (FI.of_interval (1., 2.)) + (FI.of_interval (2., 3.)) = FI.of_interval (3., 5.); (FI.of_interval (-. 2., 3.)) + (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 102., 23.); end - + let test_FI_sub_specific _ = let (-) = FI.sub in let (=) a b = assert_equal b a in @@ -74,7 +74,7 @@ struct (FI.of_interval (-. 2., 3.)) - (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 22., 103.); (FI.of_const (-. 0.)) - fi_zero = fi_zero end - + let test_FI_mul_specific _ = let ( * ) = FI.mul in let (=) a b = assert_equal b a in @@ -90,14 +90,14 @@ struct (FI.of_const fmax) * fi_one = FI.of_const fmax; (FI.of_const 2.) * (FI.of_const 0.5) = fi_one; (FI.of_interval (-. 2., 3.)) * (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 300., 200.); - + let up = if Float_t.name <> "float" then succ 1.00000000000000222 else succ (succ 1.00000000000000111 *. succ 1.00000000000000111) in begin (FI.of_const 1.00000000000000111) * (FI.of_const 1.00000000000000111) = FI.of_interval (1.00000000000000222 , up); (FI.of_const (-. 1.00000000000000111)) * (FI.of_const 1.00000000000000111) = FI.of_interval (-. up, -. 1.00000000000000222) end end - + let test_FI_div_specific _ = let (/) = FI.div in let (=) a b = assert_equal b a in @@ -117,11 +117,11 @@ struct (FI.of_const 4.) / (FI.of_const 2.) = (FI.of_const 2.); (FI.of_interval (-. 2., 3.)) / (FI.of_interval (-. 100., 20.)) = FI.top (); (FI.of_interval (6., 10.)) / (FI.of_interval (2., 3.)) = (FI.of_interval (2., 5.)); - + (FI.of_const 1.) / (FI.of_const 3.) = (FI.of_interval (pred 0.333333333333333370340767487505, 0.333333333333333370340767487505)); (FI.of_const (-. 1.)) / (FI.of_const 3.) = (FI.of_interval (-. 0.333333333333333370340767487505, succ (-. 0.333333333333333370340767487505))) end - + let test_FI_casti2f_specific _ = let cast_bool a b = assert_equal b (FI.of_int (IT.of_int IBool (Big_int_Z.big_int_of_int a))) in @@ -142,7 +142,7 @@ struct cast (IT.of_interval IInt (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)) (FI.of_interval (-. 100., 100.)); cast (IT.of_interval IUShort (Big_int_Z.big_int_of_int 2, Big_int_Z.big_int_of_int 100)) (FI.of_interval (2., 100.)); cast (IT.of_interval IShort (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)) (FI.of_interval (-. 100., 100.)); - + cast (IT.of_interval IULong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_int64 Int64.max_int)) (FI.of_interval (0., 9223372036854775807.)); cast (IT.of_interval IULong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_int64 (9223372036854775806L))) (FI.of_interval (0., 9223372036854775807.)); cast (IT.of_interval ILong (Big_int_Z.big_int_of_int64 Int64.min_int, Big_int_Z.zero_big_int)) (FI.of_interval (-. 9223372036854775808., 0.)); @@ -153,7 +153,7 @@ struct cast (IT.of_interval ILongLong (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)) (FI.of_interval (-. 100., 100.)); GobConfig.set_bool "ana.int.interval" false; end - + let test_FI_castf2i_specific _ = let cast ikind a b = OUnit2.assert_equal ~cmp:IT.equal ~printer:IT.show b (FI.to_int ikind a) in @@ -166,14 +166,14 @@ struct cast IBool (FI.of_interval (-9999999999.,9999999999.)) (IT.top_of IBool); cast IBool fi_one (IT.of_bool IBool true); cast IBool fi_zero (IT.of_bool IBool false); - + cast IUChar (FI.of_interval (0.123, 128.999)) (IT.of_interval IUChar (Big_int_Z.big_int_of_int 0, Big_int_Z.big_int_of_int 128)); cast IChar (FI.of_interval (-. 8.0000000, 127.)) (IT.of_interval IChar (Big_int_Z.big_int_of_int (-8), Big_int_Z.big_int_of_int 127)); cast IUInt (FI.of_interval (2., 100.)) (IT.of_interval IUInt (Big_int_Z.big_int_of_int 2, Big_int_Z.big_int_of_int 100)); cast IInt (FI.of_interval (-. 100.2, 100.1)) (IT.of_interval IInt (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); cast IUShort (FI.of_interval (2., 100.)) (IT.of_interval IUShort (Big_int_Z.big_int_of_int 2, Big_int_Z.big_int_of_int 100)); cast IShort (FI.of_interval (-. 100., 100.)) (IT.of_interval IShort (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); - + cast IULong (FI.of_interval (0., 9223372036854775808.)) (IT.of_interval IULong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_string "9223372036854775808")); cast ILong (FI.of_interval (-. 9223372036854775808., 0.)) (IT.of_interval ILong (Big_int_Z.big_int_of_string "-9223372036854775808", Big_int_Z.zero_big_int)); cast ILong (FI.of_interval (-. 100.99999, 100.99999)) (IT.of_interval ILong (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); @@ -182,7 +182,7 @@ struct cast ILongLong (FI.of_interval (-. 100., 100.)) (IT.of_interval ILongLong (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); GobConfig.set_bool "ana.int.interval" false; end - + let test_FI_meet_specific _ = let check_meet a b c = assert_equal c (FI.meet a b) in @@ -192,7 +192,7 @@ struct assert_raises (Failure "meet results in empty Interval") (fun () -> (FI.meet fi_zero fi_one)); check_meet (FI.of_interval (0., 10.)) (FI.of_interval (5., 20.)) (FI.of_interval (5., 10.)); end - + let test_FI_join_specific _ = let check_join a b c = assert_equal c (FI.join a b) in @@ -201,7 +201,7 @@ struct check_join (FI.top ()) fi_one (FI.top ()); check_join (FI.of_interval (0., 10.)) (FI.of_interval (5., 20.)) (FI.of_interval (0., 20.)); end - + let test_FI_leq_specific _ = let check_leq flag a b = OUnit2.assert_equal flag (FI.leq a b) in @@ -214,7 +214,7 @@ struct check_leq true (FI.of_interval (1., 19.)) (FI.of_interval (0., 20.)); check_leq false (FI.of_interval (0., 20.)) (FI.of_interval (20.0001, 20.0002)); end - + let test_FI_widen_specific _ = let check_widen a b c = assert_equal c (FI.widen a b) in @@ -226,7 +226,7 @@ struct check_widen fi_one fi_zero (FI.of_interval (fmin, 1.)); check_widen fi_one (FI.of_interval (0., 2.)) (FI.of_interval (fmin, fmax)); end - + let test_FI_narrow_specific _ = let check_narrow a b c = assert_equal c (FI.narrow a b) in @@ -236,64 +236,62 @@ struct check_narrow (FI.top ()) fi_zero fi_zero; check_narrow fi_zero fi_one fi_zero; end - - (**TODO: add tests for specific edge cases (eg. overflow to infinity when dbl.max + 1) *) - + (**interval tests using QCheck arbitraries *) let test_FI_not_bot = QCheck.Test.make ~name:"test_FI_not_bot" (FI.arbitrary ()) (fun arg -> not (FI.is_bot arg)) - + let test_FI_of_const_not_bot = QCheck.Test.make ~name:"test_FI_of_const_not_bot" QCheck.float (fun arg -> not (FI.is_bot (FI.of_const arg))) - + let test_FI_div_zero_result_top = QCheck.Test.make ~name:"test_FI_div_zero_result_top" (FI.arbitrary ()) (fun arg -> FI.is_top (FI.div arg (FI.of_const 0.))) - + let test_FI_accurate_neg = QCheck.Test.make ~name:"test_FI_accurate_neg" QCheck.float (fun arg -> FI.equal (FI.of_const (-.arg)) (FI.neg (FI.of_const arg))) - + let test_FI_lt_xor_ge = QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> itb_xor (FI.lt arg1 arg2) (FI.ge arg1 arg2)) - + let test_FI_gt_xor_le = QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> itb_xor (FI.gt arg1 arg2) (FI.le arg1 arg2)) - + let test_FI_eq_xor_ne = QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> itb_xor (FI.eq arg1 arg2) (FI.ne arg1 arg2)) - + let test_FI_add = QCheck.Test.make ~name:"test_FI_add" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.add (FI.of_const arg1) (FI.of_const arg2) in (FI.leq (FI.of_const (Option.get (to_float (add Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && (FI.leq (FI.of_const (Option.get (to_float (add Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) - + let test_FI_sub = QCheck.Test.make ~name:"test_FI_sub" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.sub (FI.of_const arg1) (FI.of_const arg2) in (FI.leq (FI.of_const (Option.get (to_float (sub Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && (FI.leq (FI.of_const (Option.get (to_float (sub Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) - + let test_FI_mul = QCheck.Test.make ~name:"test_FI_mul" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.mul (FI.of_const arg1) (FI.of_const arg2) in (FI.leq (FI.of_const (Option.get (to_float (mul Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && (FI.leq (FI.of_const (Option.get (to_float (mul Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) - - + + let test_FI_div = QCheck.Test.make ~name:"test_FI_div" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.div (FI.of_const arg1) (FI.of_const arg2) in (FI.leq (FI.of_const (Option.get (to_float (div Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && (FI.leq (FI.of_const (Option.get (to_float (div Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) - - + + let test () = [ "test_FI_nan" >:: test_FI_nan; "test_FI_add_specific" >:: test_FI_add_specific; @@ -309,7 +307,7 @@ struct "test_FI_widen_specific" >:: test_FI_widen_specific; "test_FI_narrow_specific" >:: test_FI_narrow_specific; ] - + let test_qcheck () = QCheck_ounit.to_ounit2_test_list [ test_FI_not_bot; test_FI_of_const_not_bot; @@ -324,10 +322,10 @@ struct test_FI_div; ] end - + module FloatIntervalTest32 = FloatInterval(CFloat)(FloatDomain.F32Interval) module FloatIntervalTest64 = FloatInterval(CDouble)(FloatDomain.F64Interval) - + let test () = "floatDomainTest" >::: [ @@ -336,4 +334,4 @@ let test () = "float_interval64" >::: FloatIntervalTest64.test (); "float_interval_qcheck64" >::: FloatIntervalTest64.test_qcheck (); ] - + From 449ce4571ccd919c309b3f4430b76df8ef026229 Mon Sep 17 00:00:00 2001 From: Dominik Berger Date: Thu, 16 Jun 2022 12:33:59 +0200 Subject: [PATCH 21/54] reorder test ids in float regression tests (#17) --- tests/regression/56-floats/{06-equality.c => 07-equality.c} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/regression/56-floats/{06-equality.c => 07-equality.c} (100%) diff --git a/tests/regression/56-floats/06-equality.c b/tests/regression/56-floats/07-equality.c similarity index 100% rename from tests/regression/56-floats/06-equality.c rename to tests/regression/56-floats/07-equality.c From 54693ddb7a901255695c87d4fed90122d5137c77 Mon Sep 17 00:00:00 2001 From: FelixKrayer Date: Thu, 23 Jun 2022 13:53:31 +0200 Subject: [PATCH 22/54] rename float test folder --- tests/regression/{56-floats => 57-floats}/01-base.c | 0 tests/regression/{56-floats => 57-floats}/02-node_configuration.c | 0 tests/regression/{56-floats => 57-floats}/03-infinity_or_nan.c | 0 tests/regression/{56-floats => 57-floats}/04-casts.c | 0 tests/regression/{56-floats => 57-floats}/05-invariant.c | 0 tests/regression/{56-floats => 57-floats}/06-library_functions.c | 0 tests/regression/{56-floats => 57-floats}/07-equality.c | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename tests/regression/{56-floats => 57-floats}/01-base.c (100%) rename tests/regression/{56-floats => 57-floats}/02-node_configuration.c (100%) rename tests/regression/{56-floats => 57-floats}/03-infinity_or_nan.c (100%) rename tests/regression/{56-floats => 57-floats}/04-casts.c (100%) rename tests/regression/{56-floats => 57-floats}/05-invariant.c (100%) rename tests/regression/{56-floats => 57-floats}/06-library_functions.c (100%) rename tests/regression/{56-floats => 57-floats}/07-equality.c (100%) diff --git a/tests/regression/56-floats/01-base.c b/tests/regression/57-floats/01-base.c similarity index 100% rename from tests/regression/56-floats/01-base.c rename to tests/regression/57-floats/01-base.c diff --git a/tests/regression/56-floats/02-node_configuration.c b/tests/regression/57-floats/02-node_configuration.c similarity index 100% rename from tests/regression/56-floats/02-node_configuration.c rename to tests/regression/57-floats/02-node_configuration.c diff --git a/tests/regression/56-floats/03-infinity_or_nan.c b/tests/regression/57-floats/03-infinity_or_nan.c similarity index 100% rename from tests/regression/56-floats/03-infinity_or_nan.c rename to tests/regression/57-floats/03-infinity_or_nan.c diff --git a/tests/regression/56-floats/04-casts.c b/tests/regression/57-floats/04-casts.c similarity index 100% rename from tests/regression/56-floats/04-casts.c rename to tests/regression/57-floats/04-casts.c diff --git a/tests/regression/56-floats/05-invariant.c b/tests/regression/57-floats/05-invariant.c similarity index 100% rename from tests/regression/56-floats/05-invariant.c rename to tests/regression/57-floats/05-invariant.c diff --git a/tests/regression/56-floats/06-library_functions.c b/tests/regression/57-floats/06-library_functions.c similarity index 100% rename from tests/regression/56-floats/06-library_functions.c rename to tests/regression/57-floats/06-library_functions.c diff --git a/tests/regression/56-floats/07-equality.c b/tests/regression/57-floats/07-equality.c similarity index 100% rename from tests/regression/56-floats/07-equality.c rename to tests/regression/57-floats/07-equality.c From 788c20848ca9b9b03c60a3a9160a4c115dce8725 Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Wed, 29 Jun 2022 09:42:53 +0200 Subject: [PATCH 23/54] Implement invariant and projection for float domain (#25) --- src/analyses/base.ml | 6 ++++++ src/cdomains/floatDomain.ml | 14 ++++++++++++++ src/cdomains/floatDomain.mli | 2 ++ src/cdomains/valueDomain.ml | 3 ++- 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index b4675cc50d..b13653b951 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -1127,6 +1127,12 @@ struct ID.invariant e n else Invariant.none + | `Float n -> + let e = Lval (BatOption.get c.Invariant.lval) in + if InvariantCil.(not (exp_contains_tmp e) && exp_is_in_scope scope e) then + FD.invariant e n + else + Invariant.none | `Address n -> ad_invariant ~vs ~offset c n | `Blob n -> blob_invariant ~vs ~offset c n | `Struct n -> ValueDomain.Structs.invariant ~value_invariant:(vd_invariant ~vs) ~offset c n diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 7803a64721..fd545034c5 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -374,6 +374,8 @@ module type FloatDomain = sig val is_exact : t -> bool val precision : t -> Cil.fkind + + val invariant: Cil.exp -> t -> Invariant.t end module FloatIntervalImplLifted = struct @@ -477,6 +479,17 @@ module FloatIntervalImplLifted = struct | F32 a, FDouble -> create_interval FDouble (F1.minimal a) (F1.maximal a) | F64 a, FFloat -> create_interval FFloat (F2.minimal a) (F2.maximal a) | _ -> x + + let invariant e (x:t) = + let fk = precision x in + match minimal x, maximal x with + | Some x1, Some x2 when x1 = x2 -> + Invariant.of_exp Cil.(BinOp (Eq, e, Const (CReal (x1, fk, None)), intType)) + | Some x1, Some x2 -> + let i1 = Invariant.of_exp Cil.(BinOp (Le, Const (CReal (x1, fk, None)), e, intType)) in + let i2 = Invariant.of_exp Cil.(BinOp (Le, e, Const (CReal (x2, fk, None)), intType)) in + Invariant.(&&) i1 i2 + | _ -> Invariant.none end module FloatDomTupleImpl = struct @@ -574,6 +587,7 @@ module FloatDomTupleImpl = struct let minimal x = Option.bind x F1.minimal let maximal x = Option.bind x F1.maximal + let invariant e x = Option.map_default (F1.invariant e) (Invariant.none) x let of_int fkind = create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.of_int fkind); } diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index b8a4ec51dd..6b5c275216 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -89,6 +89,8 @@ module type FloatDomain = sig val is_exact : t -> bool val precision : t -> Cil.fkind + + val invariant: Cil.exp -> t -> Invariant.t end module FloatDomTupleImpl : sig diff --git a/src/cdomains/valueDomain.ml b/src/cdomains/valueDomain.ml index af95528845..f35aa0f4e7 100644 --- a/src/cdomains/valueDomain.ml +++ b/src/cdomains/valueDomain.ml @@ -1098,7 +1098,8 @@ struct let rec project p (v: t): t = match v with | `Int n -> `Int (ID.project p n) - | `Float n -> failwith "todo - project" + (* as long as we only have one representation, project is a nop*) + | `Float n -> `Float n | `Address n -> `Address (project_addr p n) | `Struct n -> `Struct (Structs.map (fun (x: t) -> project p x) n) | `Union (f, v) -> `Union (f, project p v) From a010ad66d0f17b5d2b84727fdf448dbd668bb674 Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Wed, 29 Jun 2022 13:57:42 +0200 Subject: [PATCH 24/54] Address the comments in the PR (#24) - remane FD.precision to FD.get_fkind - rename FloatMesssage to Float - support all long double but only with the precision of a normal double - change back to old rounding mode instead of nearest - add no const prop flag to ocamlopt flags - move the hash into the FloatOps interface - remove some branches/comments --- src/analyses/base.ml | 29 ++++++++++++---------------- src/cdomains/baseDomain.ml | 1 - src/cdomains/floatDomain.ml | 23 ++++++++++------------ src/cdomains/floatDomain.mli | 11 ++++------- src/cdomains/floatOps/floatOps.ml | 3 +++ src/cdomains/floatOps/floatOps.mli | 1 + src/cdomains/floatOps/stubs.c | 4 ++-- src/dune | 1 + src/util/messageCategory.ml | 8 ++++---- unittest/cdomains/floatDomainTest.ml | 2 +- 10 files changed, 38 insertions(+), 45 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index b13653b951..edd3e71eff 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -131,7 +131,7 @@ struct let unop_FD = function | Neg -> FD.neg (* other unary operators are not implemented on float values *) - | _ -> (fun c -> FD.top_of (FD.precision c)) + | _ -> (fun c -> FD.top_of (FD.get_fkind c)) (* Evaluating Cil's unary operators. *) let evalunop op typ = function @@ -747,9 +747,9 @@ struct `Int (ID.cast_to ikind (IntDomain.of_const (num,ikind,str))) | Const (CReal (_, (FFloat | FDouble as fkind), Some str)) -> `Float (FD.of_string fkind str) (* prefer parsing from string due to higher precision *) | Const (CReal (num, (FFloat | FDouble as fkind), None)) -> `Float (FD.of_const fkind num) - (* this is so far only for DBL_MIN/DBL_MAX as it is represented as LongDouble although it would fit into a double as well *) - | Const (CReal (_, (FLongDouble), Some str)) when str = "2.2250738585072014e-308L" -> `Float (FD.of_string FDouble str) - | Const (CReal (_, (FLongDouble), Some str)) when str = "1.7976931348623157e+308L" -> `Float (FD.of_string FDouble str) + (* we also support long doubles but only with the precision of a double *) + | Const (CReal (_, (FLongDouble), Some str)) -> `Float (FD.of_string FDouble str) + | Const (CReal (num, (FLongDouble), None)) -> `Float (FD.of_const FDouble num) (* String literals *) | Const (CStr (x,_)) -> `Address (AD.from_string x) (* normal 8-bit strings, type: char* *) | Const (CWStr (xs,_) as c) -> (* wide character strings, type: wchar_t* *) @@ -1851,7 +1851,7 @@ struct | Eq | Ne as op -> let both x = x, x in let m = FD.meet a b in - let result = FD.ne c (FD.of_const (FD.precision c) 0.) in + let result = FD.ne c (FD.of_const (FD.get_fkind c) 0.) in (match op, ID.to_bool(result) with | Eq, Some true | Ne, Some false -> both m (* def. equal: if they compare equal, both values must be from the meet *) @@ -1864,15 +1864,15 @@ struct | Lt | Le | Ge | Gt as op -> (match FD.minimal a, FD.maximal a, FD.minimal b, FD.maximal b with | Some l1, Some u1, Some l2, Some u2 -> - (match op, ID.to_bool(FD.ne c (FD.of_const (FD.precision c) 0.)) with + (match op, ID.to_bool(FD.ne c (FD.of_const (FD.get_fkind c) 0.)) with | Le, Some true - | Gt, Some false -> meet_bin (FD.ending (FD.precision a) u2) (FD.starting (FD.precision b) l1) + | Gt, Some false -> meet_bin (FD.ending (FD.get_fkind a) u2) (FD.starting (FD.get_fkind b) l1) | Ge, Some true - | Lt, Some false -> meet_bin (FD.starting (FD.precision a) l2) (FD.ending (FD.precision b) u1) + | Lt, Some false -> meet_bin (FD.starting (FD.get_fkind a) l2) (FD.ending (FD.get_fkind b) u1) | Lt, Some true - | Ge, Some false -> meet_bin (FD.ending (FD.precision a) (Float.pred u2)) (FD.starting (FD.precision b) (Float.succ l1)) + | Ge, Some false -> meet_bin (FD.ending (FD.get_fkind a) (Float.pred u2)) (FD.starting (FD.get_fkind b) (Float.succ l1)) | Gt, Some true - | Le, Some false -> meet_bin (FD.starting (FD.precision a) (Float.succ l2)) (FD.ending (FD.precision b) (Float.pred u1)) + | Le, Some false -> meet_bin (FD.starting (FD.get_fkind a) (Float.succ l2)) (FD.ending (FD.get_fkind b) (Float.pred u1)) | _, _ -> a, b) | _ -> a, b) | op -> @@ -1884,12 +1884,7 @@ struct let set' lval v st = set a gs st (eval_lv a gs st lval) (Cilfacade.typeOfLval lval) v ~invariant:true ~ctx in let rec inv_exp c_typed exp (st:store): store = (* trying to improve variables in an expression so it is bottom means dead code *) - ( - match c_typed with - | `Int c -> if ID.is_bot c then raise Deadcode - | `Float c -> if FD.is_bot c then raise Deadcode - | _ -> if VD.is_bot c_typed then raise Deadcode - ); + if VD.is_bot_value c_typed then raise Deadcode; match exp, c_typed with | UnOp (LNot, e, _), `Int c -> let ikind = Cilfacade.get_ikind_exp e in @@ -1977,7 +1972,7 @@ struct | _ -> failwith "unreachable") | Const _ , _ -> st (* nothing to do *) | CastE ((TFloat (_, _)), e), `Float c -> - (match Cilfacade.typeOf e, FD.precision c with + (match Cilfacade.typeOf e, FD.get_fkind c with | TFloat (FDouble as fk, _), FFloat | TFloat (FDouble as fk, _), FDouble | TFloat (FFloat as fk, _), FFloat -> inv_exp (`Float (FD.cast_to fk c)) e st diff --git a/src/cdomains/baseDomain.ml b/src/cdomains/baseDomain.ml index 1a04125a93..5a4a8bfe81 100644 --- a/src/cdomains/baseDomain.ml +++ b/src/cdomains/baseDomain.ml @@ -158,7 +158,6 @@ module DomWithTrivialExpEval (PrivD: Lattice.S) = DomFunctor (PrivD) (struct begin match CPA.find v r.cpa with | `Int i -> ValueDomain.ID.to_int i - | `Float f -> failwith "unreachable" | _ -> None end | _ -> None diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index fd545034c5..bb2abe06d1 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -66,9 +66,7 @@ end module FloatIntervalImpl(Float_t : CFloatType) = struct include Printable.Std (* for default invariant, tag and relift *) - type t = (Float_t.t * Float_t.t) option [@@deriving eq, ord, to_yojson] - - let hash = Hashtbl.hash + type t = (Float_t.t * Float_t.t) option [@@deriving eq, ord, to_yojson, hash] let to_int ik = function | None -> IntDomain.IntDomTuple.top_of ik @@ -122,7 +120,7 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct else None | _ -> None in if is_top normed then - Messages.warn ~category:Messages.Category.FloatMessage ~tags:[CWE 189; CWE 739] + Messages.warn ~category:Messages.Category.Float ~tags:[CWE 189; CWE 739] "Float could be +/-infinity or Nan"; normed @@ -184,7 +182,7 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let high = if h1 >= h2 then h1 else Float_t.upper_bound in norm @@ Some (low, high) - let narrow v1 v2 = (**TODO: support 'threshold_narrowing' and 'narrow_by_meet' option *) + let narrow v1 v2 = match v1, v2 with (**we cannot distinguish between the lower bound beeing -inf or the upper bound beeing inf. Also there is nan *) | None, _ -> v2 | _, _ -> v1 @@ -201,7 +199,7 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let is_exact_after = is_exact (r1, r2) in if not is_exact_after && is_exact_before then Messages.warn - ~category:Messages.Category.FloatMessage + ~category:Messages.Category.Float ~tags:[CWE 197; CWE 681; CWE 1339] "The result of this operation is not exact, even though the inputs were exact."; result @@ -274,7 +272,7 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let eval_eq (l1, h1) (l2, h2) = Messages.warn - ~category:Messages.Category.FloatMessage + ~category:Messages.Category.Float ~tags:[CWE 1077] "Equality between `double` is dangerous!"; if h1 < l2 || h2 < l1 then (0, 0) @@ -283,7 +281,7 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let eval_ne (l1, h1) (l2, h2) = Messages.warn - ~category:Messages.Category.FloatMessage + ~category:Messages.Category.Float ~tags:[CWE 1077] "Equality/Inequality between `double` is dangerous!"; if h1 < l2 || h2 < l1 then (1, 1) @@ -373,8 +371,7 @@ module type FloatDomain = sig val maximal: t -> float option val is_exact : t -> bool - val precision : t -> Cil.fkind - + val get_fkind : t -> Cil.fkind val invariant: Cil.exp -> t -> Invariant.t end @@ -437,7 +434,7 @@ module FloatIntervalImplLifted = struct let top () = failwith "top () is not implemented for FloatIntervalImplLifted." let is_top = dispatch (F1.is_bot, F2.is_bot) - let precision = dispatch ((fun _ -> FFloat), (fun _ -> FDouble)) + let get_fkind = dispatch ((fun _ -> FFloat), (fun _ -> FDouble)) let leq = lift2_cmp (F1.leq, F2.leq) let join = lift2 (F1.join, F2.join) @@ -481,7 +478,7 @@ module FloatIntervalImplLifted = struct | _ -> x let invariant e (x:t) = - let fk = precision x in + let fk = get_fkind x in match minimal x, maximal x with | Some x1, Some x2 when x1 = x2 -> Invariant.of_exp Cil.(BinOp (Eq, e, Const (CReal (x1, fk, None)), intType)) @@ -583,7 +580,7 @@ module FloatDomTupleImpl = struct for_all % mapp { fp= (fun (type a) (module F : FloatDomain with type t = a) -> F.is_top); } - let precision = Option.map_default F1.precision FDouble + let get_fkind = Option.map_default F1.get_fkind FDouble let minimal x = Option.bind x F1.minimal let maximal x = Option.bind x F1.maximal diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index 6b5c275216..db7b115158 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -5,7 +5,7 @@ module type FloatArith = sig type t val neg : t -> t - (** Negating an flaot value: [-x] *) + (** Negating a float value: [-x] *) val add : t -> t -> t (** Addition: [x + y] *) val sub : t -> t -> t @@ -62,7 +62,7 @@ module type FloatDomainBase = sig val is_exact : t -> bool end -(* Only required for testing *) +(* Only exposed for testing *) module F64Interval : FloatDomainBase module F32Interval : FloatDomainBase @@ -88,11 +88,8 @@ module type FloatDomain = sig val maximal: t -> float option val is_exact : t -> bool - val precision : t -> Cil.fkind - + val get_fkind : t -> Cil.fkind val invariant: Cil.exp -> t -> Invariant.t end -module FloatDomTupleImpl : sig - include FloatDomain -end +module FloatDomTupleImpl : FloatDomain diff --git a/src/cdomains/floatOps/floatOps.ml b/src/cdomains/floatOps/floatOps.ml index ca82ca97bc..b331680507 100644 --- a/src/cdomains/floatOps/floatOps.ml +++ b/src/cdomains/floatOps/floatOps.ml @@ -22,6 +22,7 @@ module type CFloatType = sig val succ: t -> t val equal: t -> t -> bool + val hash: t -> int val compare: t -> t -> int val to_yojson: t -> Yojson.Safe.t val to_string: t -> string @@ -59,6 +60,7 @@ module CDouble = struct let pred = Float.pred let succ = Float.succ + let hash = Hashtbl.hash let to_string = Float.to_string let neg = Float.neg @@ -88,6 +90,7 @@ module CFloat = struct let is_finite x = Float.is_finite x && x >= lower_bound && x <= upper_bound + let hash = Hashtbl.hash let to_string = Float.to_string let neg = Float.neg diff --git a/src/cdomains/floatOps/floatOps.mli b/src/cdomains/floatOps/floatOps.mli index 4dd497994a..bc7982b4a2 100644 --- a/src/cdomains/floatOps/floatOps.mli +++ b/src/cdomains/floatOps/floatOps.mli @@ -22,6 +22,7 @@ module type CFloatType = sig val succ: t -> t val equal: t -> t -> bool + val hash: t -> int val compare: t -> t -> int val to_yojson: t -> Yojson.Safe.t val to_string: t -> string diff --git a/src/cdomains/floatOps/stubs.c b/src/cdomains/floatOps/stubs.c index 74bbfe030d..d3981337d3 100644 --- a/src/cdomains/floatOps/stubs.c +++ b/src/cdomains/floatOps/stubs.c @@ -39,10 +39,11 @@ static void change_round_mode(int mode) #define BINARY_OP(name, type, op) \ CAMLprim value name##_##type(value mode, value x, value y) \ { \ + int old_roundingmode = fegetround(); \ change_round_mode(Int_val(mode)); \ volatile type r, x1 = Double_val(x), y1 = Double_val(y); \ r = x1 op y1; \ - change_round_mode(Nearest); \ + fesetround(old_roundingmode); \ return caml_copy_double(r); \ } @@ -75,7 +76,6 @@ CAMLprim value atof_float(value mode, value str) return caml_copy_double(r); } - CAMLprim value max_float(value unit) { return caml_copy_double(FLT_MAX); diff --git a/src/dune b/src/dune index 84433a8908..fef6b0801e 100644 --- a/src/dune +++ b/src/dune @@ -34,6 +34,7 @@ ) ) (foreign_stubs (language c) (names stubs)) + (ocamlopt_flags :standard -no-float-const-prop) (preprocess (pps ppx_deriving.std ppx_deriving_hash ppx_deriving_yojson ppx_distr_guards ocaml-monadic ppx_blob)) diff --git a/src/util/messageCategory.ml b/src/util/messageCategory.ml index fc032d4611..34fbe6998a 100644 --- a/src/util/messageCategory.ml +++ b/src/util/messageCategory.ml @@ -27,7 +27,7 @@ type category = | Assert | Behavior of behavior | Integer of integer - | FloatMessage + | Float | Race | Deadlock | Cast of cast @@ -163,7 +163,7 @@ let should_warn e = | Assert -> "assert" | Behavior _ -> "behavior" | Integer _ -> "integer" - | FloatMessage -> "float" + | Float -> "float" | Race -> "race" | Deadlock -> "deadlock" | Cast _ -> "cast" @@ -180,7 +180,7 @@ let path_show e = | Assert -> ["Assert"] | Behavior x -> "Behavior" :: Behavior.path_show x | Integer x -> "Integer" :: Integer.path_show x - | FloatMessage -> ["Float"] + | Float -> ["Float"] | Race -> ["Race"] | Deadlock -> ["Deadlock"] | Cast x -> "Cast" :: Cast.path_show x @@ -220,7 +220,7 @@ let categoryName = function | Integer x -> (match x with | Overflow -> "Overflow"; | DivByZero -> "DivByZero") - | FloatMessage -> "Float" + | Float -> "Float" let from_string_list (s: string list) = diff --git a/unittest/cdomains/floatDomainTest.ml b/unittest/cdomains/floatDomainTest.ml index 8a75726933..249a475009 100644 --- a/unittest/cdomains/floatDomainTest.ml +++ b/unittest/cdomains/floatDomainTest.ml @@ -52,7 +52,7 @@ struct FI.top () + FI.top () = FI.top (); (FI.of_const fmin) + (FI.of_const fmax) = fi_zero; (FI.of_const fsmall) + (FI.of_const fsmall) = FI.of_const (fsmall +. fsmall); - (FI.of_const fsmall) + (FI.of_const 1.) = FI.of_interval (1., succ (1. +. fsmall)); + (FI.of_const fsmall) + (FI.of_const 1.) = FI.of_interval (1., (1. +. fsmall)); (FI.of_interval (1., 2.)) + (FI.of_interval (2., 3.)) = FI.of_interval (3., 5.); (FI.of_interval (-. 2., 3.)) + (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 102., 23.); end From 8f16da6b405221171d239d9d7825d32b8e8d0ab8 Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Thu, 30 Jun 2022 10:48:50 +0200 Subject: [PATCH 25/54] Remove reinterpret_cast/bit_cast between floats and ints to be sound in unions and ptr casts (#20) Make all casts to/from floats on bit level return top --- src/cdomains/valueDomain.ml | 21 +++-- tests/regression/57-floats/08-bit_casts.c | 31 +++++++ .../57-floats/09-svcomp_float_req_bl_1252b.c | 90 +++++++++++++++++++ 3 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 tests/regression/57-floats/08-bit_casts.c create mode 100644 tests/regression/57-floats/09-svcomp_float_req_bl_1252b.c diff --git a/src/cdomains/valueDomain.ml b/src/cdomains/valueDomain.ml index f35aa0f4e7..046a05ae29 100644 --- a/src/cdomains/valueDomain.ml +++ b/src/cdomains/valueDomain.ml @@ -327,6 +327,11 @@ struct end in let one_addr = let open Addr in function + (* only allow conversion of float pointers if source and target type are the same *) + | Addr ({ vtype = TFloat(fkind, _); _}, _) as x when (match t with TFloat (fkind', _) when fkind = fkind' -> true | _ -> false) -> x + (* do not allow conversion from/to float pointers*) + | Addr ({ vtype = TFloat(_); _}, _) -> UnknownPtr + | _ when (match t with TFloat _ -> true | _ -> false) -> UnknownPtr | Addr ({ vtype = TVoid _; _} as v, offs) when not (Cilfacade.isCharType t) -> (* we had no information about the type (e.g. malloc), so we add it; ignore for casts to char* since they're special conversions (N1570 6.3.2.3.7) *) Addr ({ v with vtype = t }, offs) (* HACK: equal varinfo with different type, causes inconsistencies down the line, when we again assume vtype being "right", but joining etc gives no consideration to which type version to keep *) | Addr (v, o) as a -> @@ -842,11 +847,17 @@ struct end | `Field (fld, offs) -> begin match x with - | `Union (`Lifted l_fld, valu) -> - let x = cast ~torg:l_fld.ftype fld.ftype valu in - let l', o' = shift_one_over l o in - do_eval_offset ask f x offs exp l' o' v t - | `Union (_, valu) -> top () + | `Union (`Lifted l_fld, value) -> + (match value, fld.ftype with + (* only return an actual value if we have a type and return actually the exact same type *) + | `Float f_value, TFloat(fkind, _) when FD.get_fkind f_value = fkind -> `Float f_value + | `Float _, t -> top_value t + | _, TFloat(fkind, _) -> `Float (FD.top_of fkind) + | _ -> + let x = cast ~torg:l_fld.ftype fld.ftype value in + let l', o' = shift_one_over l o in + do_eval_offset ask f x offs exp l' o' v t) + | `Union (_, value) -> top () | `Top -> M.debug "Trying to read a field, but the union is unknown"; top () | _ -> M.warn "Trying to read a field, but was not given a union"; top () end diff --git a/tests/regression/57-floats/08-bit_casts.c b/tests/regression/57-floats/08-bit_casts.c new file mode 100644 index 0000000000..4e08900436 --- /dev/null +++ b/tests/regression/57-floats/08-bit_casts.c @@ -0,0 +1,31 @@ +// PARAM: --enable ana.float.interval --enable warn.float + +typedef union +{ + float value; + unsigned int word; +} A; + +int main() +{ + A a; + a.word = 3212836864; + float b = a.value; + + assert(b == -1.0f); // UNKNOWN! + + A a2; + a2.value = -1.0f; + unsigned int b2 = a2.word; + + assert(b2 == 1.0f); // UNKNOWN! + + int x = 100; + float y = *(float *)(&a); + assert(y == 100.f); // UNKNOWN! + + double i = 100.0; + unsigned j = *(unsigned *)(&i); + assert(j == 100); // UNKNOWN! + return 0; +} diff --git a/tests/regression/57-floats/09-svcomp_float_req_bl_1252b.c b/tests/regression/57-floats/09-svcomp_float_req_bl_1252b.c new file mode 100644 index 0000000000..9e2030965c --- /dev/null +++ b/tests/regression/57-floats/09-svcomp_float_req_bl_1252b.c @@ -0,0 +1,90 @@ +// PARAM: --enable ana.float.interval --enable warn.float + +typedef int __int32_t; +typedef unsigned int __uint32_t; + +typedef union +{ + float value; + __uint32_t word; +} ieee_float_shape_type; + +/* + * __fpclassify Categorizes floating point value arg into the following + * categories: + * zero, subnormal, normal, infinite, NAN, or implementation-defined category. + * Returns One of FP_INFINITE, FP_NAN, FP_NORMAL, FP_SUBNORMAL, FP_ZERO or + * implementation-defined type, specifying the category of arg. + */ + +int __fpclassify_float(float x) +{ + __uint32_t w; + + do + { + ieee_float_shape_type gf_u; + gf_u.value = (x); + (w) = gf_u.word; + } while (0); + + if (w == 0x00000000 || w == 0x80000000) + return 2; + else if ((w >= 0x00800000 && w <= 0x7f7fffff) || + (w >= 0x80800000 && w <= 0xff7fffff)) + return 4; + else if ((w >= 0x00000001 && w <= 0x007fffff) || + (w >= 0x80000001 && w <= 0x807fffff)) + return 3; + else if (w == 0x7f800000 || w == 0xff800000) + return 1; + else + return 0; +} + +float fmax_float(float x, float y) +{ + if (__fpclassify_float(x) == 0) + { + return y; + } + + if (__fpclassify_float(y) == 0) + { + return x; + } + return x > y ? x : y; +} + +int __signbit_float(float x) +{ + __uint32_t w; + + do + { + ieee_float_shape_type gf_u; + gf_u.value = (x); + (w) = gf_u.word; + } while (0); + + return (w & 0x80000000) != 0; +} + +int main() +{ + + /* REQ-BL-1252: + * The fmax and fmaxf procedures shall return +0 if one argument is -0 + * and the other +0. + */ + + float x = 0.0f; + float y = -0.0f; + float res = fmax_float(x, y); + // y is -0 and x is +0, the result shall be +0 + assert(res == 0.0f); // SUCCESS + assert(__signbit_float(res) == 1); // UNKNOWN! + assert(!(res == 0.0f && __signbit_float(res) == 0)); // UNKNOWN! + + return 0; +} From dc0f0840e683bffdba019e11cd2de66da110b8f7 Mon Sep 17 00:00:00 2001 From: FelixKrayer Date: Fri, 10 Jun 2022 19:01:07 +0200 Subject: [PATCH 26/54] implemented trigonometric float functions from math.h --- src/analyses/base.ml | 35 +++++++-- src/analyses/libraryDesc.ml | 2 + src/analyses/libraryFunctions.ml | 13 ++++ src/cdomains/floatDomain.ml | 78 +++++++++++++++++++ src/cdomains/floatDomain.mli | 15 ++++ .../57-floats/06-library_functions.c | 32 +++++++- 6 files changed, 167 insertions(+), 8 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index edd3e71eff..06e6981384 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -2530,30 +2530,53 @@ struct | Some x -> assign ctx x (List.hd args) | None -> ctx.local end - (**Floating point classification and unary trigonometric functions defined in c99*) - | Unknown, (("__builtin_isfinite" | "__builtin_isinf" | "__builtin_isinf_sign" | "__builtin_isnan" | "__builtin_isnormal" | "__builtin_signbit") as name) -> + (**Floating point classification and trigonometric functions defined in c99*) + | MathH { args; }, _ -> begin match args with | [x] -> let eval_x = eval_rv (Analyses.ask_of_ctx ctx) gs st x in begin match eval_x with | `Float float_x -> let result = - begin match name with + begin match f.vname with | "__builtin_isfinite" -> `Int (ID.cast_to IInt (FD.isfinite float_x)) | "__builtin_isinf" | "__builtin_isinf_sign" -> `Int (ID.cast_to IInt (FD.isinf float_x)) | "__builtin_isnan" -> `Int (ID.cast_to IInt (FD.isnan float_x)) | "__builtin_isnormal" -> `Int (ID.cast_to IInt (FD.isnormal float_x)) | "__builtin_signbit" -> `Int (ID.cast_to IInt (FD.signbit float_x)) - | _ -> failwith "impossible matching" + | "__builtin_acos" | "acos" -> `Float (FD.acos float_x) + | "__builtin_asin" | "asin" -> `Float (FD.asin float_x) + | "__builtin_atan" | "atan" -> `Float (FD.atan float_x) + | "__builtin_cos" | "cos" -> `Float (FD.cos float_x) + | "__builtin_sin" | "sin" -> `Float (FD.sin float_x) + | "__builtin_tan" | "tan" -> `Float (FD.tan float_x) + | _ -> failwith (f.vname^" should be implemented in goblint but isn't") end in begin match lv with | Some lv_val -> set ~ctx (Analyses.ask_of_ctx ctx) gs st (eval_lv (Analyses.ask_of_ctx ctx) ctx.global st lv_val) (Cilfacade.typeOfLval lv_val) result | None -> st end - | _ -> failwith ("non-floating-point argument in call to function "^name) + | _ -> failwith ("non-floating-point argument in call to function "^f.vname) end - | _ -> failwith ("strange "^name^" arguments") + | [y; x] -> + let eval_y = eval_rv (Analyses.ask_of_ctx ctx) gs st y in + let eval_x = eval_rv (Analyses.ask_of_ctx ctx) gs st x in + begin match eval_y, eval_x with + | `Float float_y, `Float float_x -> + let result = + begin match f.vname with + | "__builtin_atan2" | "atan2" -> `Float (FD.atan (FD.div float_y float_x)) + | _ -> failwith (f.vname^" should be implemented in goblint but isn't") + end + in + begin match lv with + | Some lv_val -> set ~ctx (Analyses.ask_of_ctx ctx) gs st (eval_lv (Analyses.ask_of_ctx ctx) ctx.global st lv_val) (Cilfacade.typeOfLval lv_val) result + | None -> st + end + | _ -> failwith ("non-floating-point argument in call to function "^f.vname) + end + | _ -> failwith ("strange "^f.vname^" arguments") end (* handling thread creations *) | ThreadCreate _, _ -> diff --git a/src/analyses/libraryDesc.ml b/src/analyses/libraryDesc.ml index 1d6eb5d38a..075f5e39fd 100644 --- a/src/analyses/libraryDesc.ml +++ b/src/analyses/libraryDesc.ml @@ -22,6 +22,7 @@ type special = | Unlock of Cil.exp | ThreadCreate of { thread: Cil.exp; start_routine: Cil.exp; arg: Cil.exp; } | ThreadJoin of { thread: Cil.exp; ret_var: Cil.exp; } + | MathH of { args: (Cil.exp list);} | Memset of { dest: Cil.exp; ch: Cil.exp; count: Cil.exp; } | Bzero of { dest: Cil.exp; count: Cil.exp; } | Abort @@ -88,6 +89,7 @@ let special_of_old classify_name = fun args -> | `Unlock -> Unlock (List.hd args) | `ThreadCreate (thread, start_routine, arg) -> ThreadCreate { thread; start_routine; arg; } | `ThreadJoin (thread, ret_var) -> ThreadJoin { thread; ret_var; } + | `MathH args -> MathH { args; } | `Unknown _ -> Unknown let of_old ?(attrs: attr list=[]) (old_accesses: Accesses.old) (classify_name): t = { diff --git a/src/analyses/libraryFunctions.ml b/src/analyses/libraryFunctions.ml index 93d8583965..68268a442a 100644 --- a/src/analyses/libraryFunctions.ml +++ b/src/analyses/libraryFunctions.ml @@ -80,6 +80,7 @@ type categories = [ | `Unlock | `ThreadCreate of exp * exp * exp (* id * f * x *) | `ThreadJoin of exp * exp (* id * ret_var *) + | `MathH of exp list | `Unknown of string ] @@ -133,6 +134,18 @@ let classify fn exps = | "pthread_mutex_unlock" | "__pthread_mutex_unlock" | "spin_unlock_irqrestore" | "up_read" | "up_write" | "up" -> `Unlock + | "__builtin_isfinite" | "__builtin_isinf" | "__builtin_isinf_sign" | "__builtin_isnan" | "__builtin_isnormal" + | "__builtin_signbit" | "__builtin_acos" | "acos" | "__builtin_asin" | "asin" | "__builtin_atan" | "atan" + | "__builtin_cos" | "cos" | "__builtin_sin" | "sin" | "__builtin_tan" | "tan" -> + begin match exps with + | [x] -> `MathH exps + | _ -> strange_arguments () + end + | "__builtin_atan2" | "atan2" -> + begin match exps with + | [y; x] -> `MathH exps + | _ -> strange_arguments () + end | x -> `Unknown x diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index bb2abe06d1..8451b6f872 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -17,6 +17,21 @@ module type FloatArith = sig val div : t -> t -> t (** Division: [x / y] *) + (** {unary functions} *) + val acos : t -> t + (** acos(x) *) + val asin : t -> t + (** asin(x) *) + val atan : t -> t + (** atan(x) *) + val cos : t -> t + (** cos(x) *) + val sin : t -> t + (** sin(x) *) + val tan : t -> t + (** tan(x) *) + + (** {b Comparison operators} *) val lt : t -> t -> IntDomain.IntDomTuple.t (** Less than: [x < y] *) @@ -344,6 +359,50 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | Some _ -> unknown_IInt (**any interval containing zero has to fall in this case, because we do not distinguish between 0. and -0. *) | None -> unknown_IInt + (**This Constant overapproximates pi to use as bounds for the return values of trigonometric functions *) + let overapprox_pi = 3.1416 + + let acos op = + match op with + | Some (l, _) when (is_exact op) && l = Float_t.of_float Nearest 1. -> of_const 0. (*acos(1) = 0*) + | Some (l, h) -> + if l < (Float_t.of_float Down (-.1.)) || h > (Float_t.of_float Up 1.) then + Messages.warn ~category:Messages.Category.Float "Domain error will occur: acos argument is outside of [-1., 1.]"; + of_interval (0., (overapprox_pi)) (**could be more exact *) + | None -> top () + + let asin op = + match op with + | Some (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*asin(0) = 0*) + | Some (l, h) -> + if l < (Float_t.of_float Down (-.1.)) || h > (Float_t.of_float Up 1.) then + Messages.warn ~category:Messages.Category.Float "Domain error will occur: asin argument is outside of [-1., 1.]"; + div (of_interval ((-. overapprox_pi), overapprox_pi)) (of_const 2.) (**could be more exact *) + | None -> top () + + let atan op = + match op with + | Some (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*atan(0) = 0*) + | Some _ -> div (of_interval ((-. overapprox_pi), overapprox_pi)) (of_const 2.) (**could be more exact *) + | None -> top () + + let cos op = + match op with + | Some (l, _) when (is_exact op) && l = Float_t.zero -> of_const 1. (*cos(0) = 1*) + | Some _ -> of_interval (-. 1., 1.) (**could be exact for intervals where l=h, or even for some intervals *) + | None -> top () + + let sin op = + match op with + | Some (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*sin(0) = 0*) + | Some _ -> of_interval (-. 1., 1.) (**could be exact for intervals where l=h, or even for some intervals *) + | None -> top () + + let tan op = + match op with + | Some (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*tan(0) = 0*) + | _ -> top () (**could be exact for intervals where l=h, or even for some intervals *) + end module F64Interval = FloatIntervalImpl(CDouble) @@ -411,6 +470,12 @@ module FloatIntervalImplLifted = struct failwith "unsupported fkind" let neg = lift (F1.neg, F2.neg) + let acos = lift (F1.acos, F2.acos) + let asin = lift (F1.asin, F2.asin) + let atan = lift (F1.atan, F2.atan) + let cos = lift (F1.cos, F2.cos) + let sin = lift (F1.sin, F2.sin) + let tan = lift (F1.tan, F2.tan) let add = lift2 (F1.add, F2.add) let sub = lift2 (F1.sub, F2.sub) let mul = lift2 (F1.mul, F2.mul) @@ -605,6 +670,19 @@ module FloatDomTupleImpl = struct (* f1: one and only unary op *) let neg = map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.neg); } + (* f1: unary functions *) + let acos = + map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.acos); } + let asin = + map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.asin); } + let atan = + map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.atan); } + let cos = + map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.cos); } + let sin = + map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.sin); } + let tan = + map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.tan); } (* f2: binary ops *) let join = diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index db7b115158..a42320065b 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -15,6 +15,21 @@ module type FloatArith = sig val div : t -> t -> t (** Division: [x / y] *) + (** {unary functions} *) + val acos : t -> t + (** acos(x) *) + val asin : t -> t + (** asin(x) *) + val atan : t -> t + (** atan(x) *) + val cos : t -> t + (** cos(x) *) + val sin : t -> t + (** sin(x) *) + val tan : t -> t + (** tan(x) *) + + (** {b Comparison operators} *) val lt : t -> t -> IntDomain.IntDomTuple.t (** Less than: [x < y] *) diff --git a/tests/regression/57-floats/06-library_functions.c b/tests/regression/57-floats/06-library_functions.c index 5d1e58432e..442a1df7be 100644 --- a/tests/regression/57-floats/06-library_functions.c +++ b/tests/regression/57-floats/06-library_functions.c @@ -1,4 +1,4 @@ -// PARAM: --enable ana.float.interval --enable ana.int.interval +// PARAM: --enable ana.float.interval --enable ana.int.interval --enable warn.float #include #include #include @@ -44,8 +44,36 @@ int main() { assert(__builtin_signbit(- inf)); //UNKNOWN assert(__builtin_signbit(nan)); //UNKNOWN + double greater_than_pi = 3.142; + //acos(x): + assert((0. <= acos(0.1)) && (acos(0.1) <= greater_than_pi)); //SUCCESS! + assert(acos(1.) == 0.); //SUCCESS! + acos(2.0); //WARN: Domain error will occur: acos argument is outside of [-1., 1.] + + //asin(x): + assert(((- greater_than_pi / 2.) <= asin(0.1)) && (asin(0.1) <= (greater_than_pi / 2.))); //SUCCESS! + assert(asin(0.) == 0.); //SUCCESS! + asin(2.0); //WARN: Domain error will occur: asin argument is outside of [-1., 1.] + + //atan(x): + assert(((- greater_than_pi / 2.) <= atan(0.1)) && (atan(0.1) <= (greater_than_pi / 2.))); //SUCCESS! + assert(atan(0.) == 0.); //SUCCESS! + + //atan2(y, x) + assert(((- greater_than_pi / 2.) <= atan2(0.1, 0.2)) && (atan2(0.1, 0.2) <= (greater_than_pi / 2.))); //SUCCESS! + + //cos(x) + assert((-1. <= cos(0.1)) && (cos(0.1) <= 1.)); //SUCCESS! + assert(cos(0.) == 1.); //SUCCESS! + + //sin(x) + assert((-1. <= sin(0.1)) && (sin(0.1) <= 1.)); //SUCCESS! + assert(sin(0.) == 0.); //SUCCESS! + + //tan(x) + assert(tan(0.) == 0.); //SUCCESS! + //unimplemented math.h function, should not invalidate globals: - cos(0.1); //NOWARN j0(0.1); //NOWARN ldexp(0.1, 1); //NOWARN } From 49352614236f66ff45cdfa759652fe9fafad0672 Mon Sep 17 00:00:00 2001 From: FelixKrayer Date: Tue, 28 Jun 2022 19:45:05 +0200 Subject: [PATCH 27/54] implement suggestions --- src/analyses/base.ml | 2 +- src/analyses/libraryDesc.ml | 4 ++-- src/analyses/libraryFunctions.ml | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 06e6981384..6db7ed9c4f 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -2531,7 +2531,7 @@ struct | None -> ctx.local end (**Floating point classification and trigonometric functions defined in c99*) - | MathH { args; }, _ -> + | Math { args; }, _ -> begin match args with | [x] -> let eval_x = eval_rv (Analyses.ask_of_ctx ctx) gs st x in diff --git a/src/analyses/libraryDesc.ml b/src/analyses/libraryDesc.ml index 075f5e39fd..5072d47f19 100644 --- a/src/analyses/libraryDesc.ml +++ b/src/analyses/libraryDesc.ml @@ -22,7 +22,7 @@ type special = | Unlock of Cil.exp | ThreadCreate of { thread: Cil.exp; start_routine: Cil.exp; arg: Cil.exp; } | ThreadJoin of { thread: Cil.exp; ret_var: Cil.exp; } - | MathH of { args: (Cil.exp list);} + | Math of { args: (Cil.exp list); } | Memset of { dest: Cil.exp; ch: Cil.exp; count: Cil.exp; } | Bzero of { dest: Cil.exp; count: Cil.exp; } | Abort @@ -89,7 +89,7 @@ let special_of_old classify_name = fun args -> | `Unlock -> Unlock (List.hd args) | `ThreadCreate (thread, start_routine, arg) -> ThreadCreate { thread; start_routine; arg; } | `ThreadJoin (thread, ret_var) -> ThreadJoin { thread; ret_var; } - | `MathH args -> MathH { args; } + | `Math args -> Math { args; } | `Unknown _ -> Unknown let of_old ?(attrs: attr list=[]) (old_accesses: Accesses.old) (classify_name): t = { diff --git a/src/analyses/libraryFunctions.ml b/src/analyses/libraryFunctions.ml index 68268a442a..d29244e977 100644 --- a/src/analyses/libraryFunctions.ml +++ b/src/analyses/libraryFunctions.ml @@ -80,7 +80,7 @@ type categories = [ | `Unlock | `ThreadCreate of exp * exp * exp (* id * f * x *) | `ThreadJoin of exp * exp (* id * ret_var *) - | `MathH of exp list + | `Math of exp list | `Unknown of string ] @@ -138,12 +138,12 @@ let classify fn exps = | "__builtin_signbit" | "__builtin_acos" | "acos" | "__builtin_asin" | "asin" | "__builtin_atan" | "atan" | "__builtin_cos" | "cos" | "__builtin_sin" | "sin" | "__builtin_tan" | "tan" -> begin match exps with - | [x] -> `MathH exps + | [x] -> `Math exps | _ -> strange_arguments () end | "__builtin_atan2" | "atan2" -> begin match exps with - | [y; x] -> `MathH exps + | [y; x] -> `Math exps | _ -> strange_arguments () end | x -> `Unknown x From fcf85c1a19c95fdbe3954848486e4e851e71bd55 Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Mon, 4 Jul 2022 17:29:31 +0200 Subject: [PATCH 28/54] Reset to old rounding mode in all c functions now (#27) --- src/cdomains/floatOps/stubs.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cdomains/floatOps/stubs.c b/src/cdomains/floatOps/stubs.c index d3981337d3..e0485883dd 100644 --- a/src/cdomains/floatOps/stubs.c +++ b/src/cdomains/floatOps/stubs.c @@ -60,9 +60,10 @@ CAMLprim value atof_double(value mode, value str) { const char *s = String_val(str); volatile double r; + int old_roundingmode = fegetround(); change_round_mode(Int_val(mode)); r = atof(s); - change_round_mode(Nearest); + fesetround(old_roundingmode); return caml_copy_double(r); } @@ -70,9 +71,10 @@ CAMLprim value atof_float(value mode, value str) { const char *s = String_val(str); volatile float r; + int old_roundingmode = fegetround(); change_round_mode(Int_val(mode)); r = (float)atof(s); - change_round_mode(Nearest); + fesetround(old_roundingmode); return caml_copy_double(r); } From fde2640919d911cda22d594294f7ce9f3b021bbd Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Mon, 4 Jul 2022 17:40:08 +0200 Subject: [PATCH 29/54] Fix invariant computation according to IEEE754 (#21) - Fix invariant computation according to IEE754 - Treat casting to bool different than normal integers --- src/analyses/base.ml | 108 ++++++++++++++++++---- src/cdomains/floatDomain.ml | 16 ++++ src/cdomains/floatDomain.mli | 4 + tests/regression/57-floats/05-invariant.c | 12 +-- 4 files changed, 114 insertions(+), 26 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 6db7ed9c4f..bc32e3b9f3 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -1832,29 +1832,97 @@ struct a, b in let inv_bin_float (a, b) fkind c op = + let open Stdlib in let meet_bin a' b' = FD.meet a a', FD.meet b b' in - let meet_com oi = (* commutative *) - meet_bin (oi c b) (oi c a) in - let meet_non oi oo = (* non-commutative *) - meet_bin (oi c b) (oo a c) in match op with - | PlusA -> meet_com FD.sub + | PlusA -> + (* A + B = C, \forall a \in A. a + b_min > pred c_min \land a + b_max < succ c_max + \land a + b_max > pred c_min \land a + b_min < succ c_max + \rightarrow A = [min(pred c_min - b_min, pred c_min - b_max), max(succ c_max - b_max, succ c_max - b_min)] + \rightarrow A = [pred c_min - b_max, succ c_max - b_min] + *) + let reverse_add v v' = (match FD.minimal c, FD.maximal c, FD.minimal v, FD.maximal v with + | Some c_min, Some c_max, Some v_min, Some v_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let l = Float.pred c_min -. v_max in + let h = Float.succ c_max -. v_min in + FD.of_interval (FD.get_fkind c) (l, h) + | _ -> v') in + meet_bin (reverse_add b a) (reverse_add a b) + | MinusA -> + (* A - B = C \ forall a \in A. a - b_max > pred c_min \land a - b_min < succ c_max + \land a - b_min > pred c_min \land a - b_max < succ c_max + \rightarrow A = [min(pred c_min + b_max, pred c_min + b_min), max(succ c_max + b_max, succ c_max + b_max)] + \rightarrow A = [pred c_min + b_min, succ c_max + b_max] + *) + let a' = (match FD.minimal c, FD.maximal c, FD.minimal b, FD.maximal b with + | Some c_min, Some c_max, Some b_min, Some b_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let l = Float.pred c_min +. b_min in + let h = Float.succ c_max +. b_max in + FD.of_interval (FD.get_fkind c) (l, h) + | _ -> a) in + (* A - B = C \ forall b \in B. a_min - b > pred c_min \land a_max - b < succ c_max + \land a_max - b > pred c_min \land a_min - b < succ c_max + \rightarrow B = [min(a_max - succ c_max, a_min - succ c_max), max(a_min - pred c_min, a_max - pred c_min)] + \rightarrow B = [a_min - succ c_max, a_max - pred c_min] + *) + let b' = (match FD.minimal c, FD.maximal c, FD.minimal a, FD.maximal a with + | Some c_min, Some c_max, Some a_min, Some a_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let l = a_min -. Float.succ c_max in + let h = a_max -. Float.pred c_min in + FD.of_interval (FD.get_fkind c) (l, h) + | _ -> b) in + meet_bin a' b' | Mult -> - (* refine x by information about y, using x * y == c *) - let refine_by x y = if FD.is_exact y then FD.meet x (FD.div c y) else x - in - (refine_by a b, refine_by b a) - | MinusA -> meet_non FD.add FD.sub - | Div -> - (* If b must be zero, we have must UB *) - meet_bin (FD.mul b c) (FD.div a c) + (* A * B = C \forall a \in A, a > 0. a * b_min > pred c_min \land a * b_max < succ c_max + A * B = C \forall a \in A, a < 0. a * b_max > pred c_min \land a * b_min < succ c_max + (with negative b reversed <>) + \rightarrow A = [min(pred c_min / b_min, pred c_min / b_max, succ c_max / b_min, succ c_max /b_max), + max(succ c_max / b_min, succ c_max /b_max, pred c_min / b_min, pred c_min / b_max)] + *) + let reverse_mul v v' = (match FD.minimal c, FD.maximal c, FD.minimal v, FD.maximal v with + | Some c_min, Some c_max, Some v_min, Some v_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let v1, v2, v3, v4 = (Float.pred c_min /. v_min), (Float.pred c_min /. v_max), (Float.succ c_max /. v_min), (Float.succ c_max /. v_max) in + let l = Float.min (Float.min v1 v2) (Float.min v3 v4) in + let h = Float.max (Float.max v1 v2) (Float.max v3 v4) in + FD.of_interval (FD.get_fkind c) (l, h) + | _ -> v') in + meet_bin (reverse_mul b a) (reverse_mul a b) + | Div -> + (* A / B = C \forall a \in A, a > 0, b_min > 1. a / b_max > pred c_min \land a / b_min < succ c_max + A / B = C \forall a \in A, a < 0, b_min > 1. a / b_min > pred c_min \land a / b_max < succ c_max + A / B = C \forall a \in A, a > 0, 0 < b_min, b_max < 1. a / b_max > pred c_min \land a / b_min < succ c_max + A / B = C \forall a \in A, a < 0, 0 < b_min, b_max < 1. a / b_min > pred c_min \land a / b_max < succ c_max + ... same for negative b + \rightarrow A = [min(b_max * pred c_min, b_min * pred c_min, b_min * succ c_max, b_max * succ c_max), + max(b_max * succ c_max, b_min * succ c_max, b_max * pred c_min, b_min * pred c_min)] + *) + let a' = (match FD.minimal c, FD.maximal c, FD.minimal b, FD.maximal b with + | Some c_min, Some c_max, Some b_min, Some b_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let v1, v2, v3, v4 = (Float.pred c_min *. b_max), (Float.pred c_min *. b_min), (Float.succ c_max *. b_max), (Float.succ c_max *. b_min) in + let l = Float.min (Float.min v1 v2) (Float.min v3 v4) in + let h = Float.max (Float.max v1 v2) (Float.max v3 v4) in + FD.of_interval (FD.get_fkind c) (l, h) + | _ -> a) in + (* A / B = C \forall b \in B, b > 0, a_min / b > pred c_min \land a_min / b < succ c_max + \land a_max / b > pred c_min \land a_max / b < succ c_max + A / B = C \forall b \in B, b < 0, a_min / b > pred c_min \land a_min / b < succ c_max + \land a_max / b > pred c_min \land a_max / b < succ c_max + \rightarrow (b != 0) B = [min(a_min / succ c_max, a_max / succ c_max, a_min / pred c_min, a_max / pred c_min), + max(a_min / pred c_min, a_max / pred c_min, a_min / succ c_max, a_max / succ c_max)] + *) + let b' = (match FD.minimal c, FD.maximal c, FD.minimal a, FD.maximal a with + | Some c_min, Some c_max, Some a_min, Some a_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let v1, v2, v3, v4 = (a_min /. Float.pred c_min), (a_max /. Float.pred c_min), (a_min /. Float.succ c_max), (a_max /. Float.succ c_max) in + let l = Float.min (Float.min v1 v2) (Float.min v3 v4) in + let h = Float.max (Float.max v1 v2) (Float.max v3 v4) in + FD.of_interval (FD.get_fkind c) (l, h) + | _ -> b) in + meet_bin a' b' | Eq | Ne as op -> let both x = x, x in - let m = FD.meet a b in - let result = FD.ne c (FD.of_const (FD.get_fkind c) 0.) in - (match op, ID.to_bool(result) with + (match op, ID.to_bool (FD.to_int IBool c) with | Eq, Some true - | Ne, Some false -> both m (* def. equal: if they compare equal, both values must be from the meet *) + | Ne, Some false -> both (FD.meet a b) (* def. equal: if they compare equal, both values must be from the meet *) | Eq, Some false | Ne, Some true -> (* def. unequal *) (* M.debug ~category:Analyzer "Can't use uneqal information about float value in expression \"%a\"." d_plainexp exp; *) @@ -1864,15 +1932,15 @@ struct | Lt | Le | Ge | Gt as op -> (match FD.minimal a, FD.maximal a, FD.minimal b, FD.maximal b with | Some l1, Some u1, Some l2, Some u2 -> - (match op, ID.to_bool(FD.ne c (FD.of_const (FD.get_fkind c) 0.)) with + (match op, ID.to_bool (FD.to_int IBool c) with | Le, Some true | Gt, Some false -> meet_bin (FD.ending (FD.get_fkind a) u2) (FD.starting (FD.get_fkind b) l1) | Ge, Some true | Lt, Some false -> meet_bin (FD.starting (FD.get_fkind a) l2) (FD.ending (FD.get_fkind b) u1) | Lt, Some true - | Ge, Some false -> meet_bin (FD.ending (FD.get_fkind a) (Float.pred u2)) (FD.starting (FD.get_fkind b) (Float.succ l1)) + | Ge, Some false -> meet_bin (FD.ending_before (FD.get_fkind a) u2) (FD.starting_after (FD.get_fkind b) l1) | Gt, Some true - | Le, Some false -> meet_bin (FD.starting (FD.get_fkind a) (Float.succ l2)) (FD.ending (FD.get_fkind b) (Float.pred u1)) + | Le, Some false -> meet_bin (FD.starting_after (FD.get_fkind a) l2) (FD.ending_before (FD.get_fkind b) u1) | _, _ -> a, b) | _ -> a, b) | op -> diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 8451b6f872..699fe06964 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -72,6 +72,8 @@ module type FloatDomainBase = sig val ending : float -> t val starting : float -> t + val ending_before : float -> t + val starting_after : float -> t val minimal: t -> float option val maximal: t -> float option @@ -85,6 +87,10 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let to_int ik = function | None -> IntDomain.IntDomTuple.top_of ik + (* special treatment for booleans as those aren't "just" truncated *) + | Some (l, h) when ik = IBool && (l > Float_t.zero || h < Float_t.zero) -> IntDomain.IntDomTuple.of_bool IBool true + | Some (l, h) when ik = IBool && l = h && l = Float_t.zero -> IntDomain.IntDomTuple.of_bool IBool false + | Some (l, h) when ik = IBool -> IntDomain.IntDomTuple.top_of IBool | Some (l, h) -> (* as converting from float to integer is (exactly) defined as leaving out the fractional part, (value is truncated towrad zero) we do not require specific rounding here *) @@ -159,7 +165,9 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let of_const f = of_interval (f, f) let ending e = of_interval' (Float_t.lower_bound, Float_t.of_float Up e) + let ending_before e = of_interval' (Float_t.lower_bound, Float_t.pred @@ Float_t.of_float Up e) let starting s = of_interval' (Float_t.of_float Down s, Float_t.upper_bound) + let starting_after s = of_interval' (Float_t.succ @@ Float_t.of_float Down s, Float_t.upper_bound) let minimal x = Option.bind x (fun (l, _) -> Float_t.to_float l) let maximal x = Option.bind x (fun (_, h) -> Float_t.to_float h) @@ -425,6 +433,8 @@ module type FloatDomain = sig val ending : Cil.fkind -> float -> t val starting : Cil.fkind -> float -> t + val ending_before : Cil.fkind -> float -> t + val starting_after : Cil.fkind -> float -> t val minimal: t -> float option val maximal: t -> float option @@ -525,7 +535,9 @@ module FloatIntervalImplLifted = struct let of_int fkind i = dispatch_fkind fkind ((fun () -> F1.of_int i), (fun () -> F2.of_int i)) let of_interval fkind i = dispatch_fkind fkind ((fun () -> F1.of_interval i), (fun () -> F2.of_interval i)) let starting fkind s = dispatch_fkind fkind ((fun () -> F1.starting s), (fun () -> F2.starting s)) + let starting_after fkind s = dispatch_fkind fkind ((fun () -> F1.starting_after s), (fun () -> F2.starting_after s)) let ending fkind e = dispatch_fkind fkind ((fun () -> F1.ending e), (fun () -> F2.ending e)) + let ending_before fkind e = dispatch_fkind fkind ((fun () -> F1.ending_before e), (fun () -> F2.ending_before e)) let minimal = dispatch (F1.minimal, F2.minimal) let maximal = dispatch (F1.maximal, F2.maximal) let to_int ikind = dispatch (F1.to_int ikind, F2.to_int ikind) @@ -622,6 +634,10 @@ module FloatDomTupleImpl = struct create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.ending fkind); } let starting fkind = create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.starting fkind); } + let ending_before fkind = + create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.ending_before fkind); } + let starting_after fkind = + create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.starting_after fkind); } let of_string fkind = create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.of_string fkind); } diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index a42320065b..6c6313101d 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -70,6 +70,8 @@ module type FloatDomainBase = sig val ending : float -> t val starting : float -> t + val ending_before : float -> t + val starting_after : float -> t val minimal: t -> float option val maximal: t -> float option @@ -98,6 +100,8 @@ module type FloatDomain = sig val ending : Cil.fkind -> float -> t val starting : Cil.fkind -> float -> t + val ending_before : Cil.fkind -> float -> t + val starting_after : Cil.fkind -> float -> t val minimal: t -> float option val maximal: t -> float option diff --git a/tests/regression/57-floats/05-invariant.c b/tests/regression/57-floats/05-invariant.c index 34cf95b226..8abdee7aee 100644 --- a/tests/regression/57-floats/05-invariant.c +++ b/tests/regression/57-floats/05-invariant.c @@ -56,29 +56,29 @@ int main() if (a + 5.f < 10.f) { - assert(a < 5.); // SUCCESS! + assert(a <= 5.); // SUCCESS! } if (b + 5.f < 10.f) { - assert(b < 5.f); // SUCCESS! + assert(b <= 5.f); // SUCCESS! } if (a * 2. < 6.f) { - assert(a < 3.); // SUCCESS! + assert(a <= 3.); // SUCCESS! } if (b * 2.f < 6.f) { - assert(b < 3.f); // SUCCESS! + assert(b <= 3.f); // SUCCESS! } if (a / 3. > 10.) { - assert(a > 30.); // SUCCESS! + assert(a >= 30.); // SUCCESS! } if (b / 3.f > 10.f) { - assert(b > 30.); // SUCCESS! + assert(b >= 30); // SUCCESS! } if (a < 10) From b0fa916efdd2b26d696701905a342e00e9a37a9a Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Tue, 5 Jul 2022 08:29:33 +0200 Subject: [PATCH 30/54] Check long doubles support and add tests for them (#26) --- src/analyses/base.ml | 17 ++- src/cdomains/floatDomain.ml | 61 ++++++----- src/cdomains/valueDomain.ml | 8 +- src/util/options.schema.json | 2 +- tests/regression/57-floats/01-base.c | 25 +++-- .../57-floats/02-node_configuration.c | 4 +- .../regression/57-floats/03-infinity_or_nan.c | 12 +- tests/regression/57-floats/04-casts.c | 103 +++++++++++------- tests/regression/57-floats/05-invariant.c | 55 +++++++--- .../57-floats/06-library_functions.c | 101 ++++++++--------- tests/regression/57-floats/07-equality.c | 6 +- tests/regression/57-floats/08-bit_casts.c | 2 +- .../57-floats/09-svcomp_float_req_bl_1252b.c | 2 +- 13 files changed, 234 insertions(+), 164 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index bc32e3b9f3..2845c9be28 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -745,11 +745,8 @@ struct | Const (CInt (num,ikind,str)) -> (match str with Some x -> M.tracel "casto" "CInt (%s, %a, %s)\n" (Cilint.string_of_cilint num) d_ikind ikind x | None -> ()); `Int (ID.cast_to ikind (IntDomain.of_const (num,ikind,str))) - | Const (CReal (_, (FFloat | FDouble as fkind), Some str)) -> `Float (FD.of_string fkind str) (* prefer parsing from string due to higher precision *) - | Const (CReal (num, (FFloat | FDouble as fkind), None)) -> `Float (FD.of_const fkind num) - (* we also support long doubles but only with the precision of a double *) - | Const (CReal (_, (FLongDouble), Some str)) -> `Float (FD.of_string FDouble str) - | Const (CReal (num, (FLongDouble), None)) -> `Float (FD.of_const FDouble num) + | Const (CReal (_, fkind, Some str)) -> `Float (FD.of_string fkind str) (* prefer parsing from string due to higher precision *) + | Const (CReal (num, fkind, None)) -> `Float (FD.of_const fkind num) (* String literals *) | Const (CStr (x,_)) -> `Address (AD.from_string x) (* normal 8-bit strings, type: char* *) | Const (CWStr (xs,_) as c) -> (* wide character strings, type: wchar_t* *) @@ -1831,7 +1828,7 @@ struct if M.tracing then M.tracel "inv" "Unhandled operator %a\n" d_binop op; a, b in - let inv_bin_float (a, b) fkind c op = + let inv_bin_float (a, b) c op = let open Stdlib in let meet_bin a' b' = FD.meet a a', FD.meet b b' in match op with @@ -1985,7 +1982,7 @@ struct st'' | `Float a, `Float b -> let fkind = Cilfacade.get_fkind_exp e1 in (* both operands have the same type *) - let a', b' = inv_bin_float (a, b) fkind (c_float fkind) op in + let a', b' = inv_bin_float (a, b) (c_float fkind) op in if M.tracing then M.tracel "inv" "binop: %a, a': %a, b': %a\n" d_exp e FD.pretty a' FD.pretty b'; let st' = inv_exp (`Float a') e1 st in let st'' = inv_exp (`Float b') e2 st' in @@ -2041,11 +2038,13 @@ struct | Const _ , _ -> st (* nothing to do *) | CastE ((TFloat (_, _)), e), `Float c -> (match Cilfacade.typeOf e, FD.get_fkind c with + | TFloat (FLongDouble as fk, _), FFloat | TFloat (FDouble as fk, _), FFloat + | TFloat (FLongDouble as fk, _), FDouble + | TFloat (fk, _), FLongDouble | TFloat (FDouble as fk, _), FDouble | TFloat (FFloat as fk, _), FFloat -> inv_exp (`Float (FD.cast_to fk c)) e st - | TFloat (FFloat, _), FDouble -> fallback ("CastE: e evaluates to FFloat which can not be used as FDouble") st - | _ -> fallback ("CastE: e has not type TFloat") st) + | _ -> fallback ("CastE: incompatible types") st) | CastE ((TInt (ik, _)) as t, e), `Int c | CastE ((TEnum ({ekind = ik; _ }, _)) as t, e), `Int c -> (* Can only meet the t part of an Lval in e with c (unless we meet with all overflow possibilities)! Since there is no good way to do this, we only continue if e has no values outside of t. *) (match eval e st with diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 699fe06964..fca518483c 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -108,8 +108,8 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | _, _ -> None let show = function - | None -> Float_t.name ^ ": [Top]" - | Some (low, high) -> Printf.sprintf "%s:[%s,%s]" Float_t.name (Float_t.to_string low) (Float_t.to_string high) + | None -> "[Top]" + | Some (low, high) -> Printf.sprintf "[%s,%s]" (Float_t.to_string low) (Float_t.to_string high) let pretty () x = text (show x) @@ -446,38 +446,48 @@ end module FloatIntervalImplLifted = struct include Printable.Std (* for default invariant, tag and relift *) - type t = F32 of F32Interval.t | F64 of F64Interval.t [@@deriving to_yojson, eq, ord, hash] + type t = + | F32 of F32Interval.t + | F64 of F64Interval.t + | FLong of F64Interval.t [@@deriving to_yojson, eq, ord, hash] module F1 = F32Interval module F2 = F64Interval + let show = function + | F32 a -> "float: " ^ F1.show a + | F64 a -> "double: " ^ F2.show a + | FLong a -> "long double: " ^ F2.show a + let lift2 (op32, op64) x y = match x, y with | F32 a, F32 b -> F32 (op32 a b) | F64 a, F64 b -> F64 (op64 a b) - | F32 a, F64 b -> failwith ("fkinds float and double are incompatible. Values: " ^ Prelude.Ana.sprint F32Interval.pretty a ^ " and " ^ Prelude.Ana.sprint F64Interval.pretty b) - | F64 a, F32 b -> failwith ("fkinds double and float are incompatible. Values: " ^ Prelude.Ana.sprint F64Interval.pretty a ^ " and " ^ Prelude.Ana.sprint F32Interval.pretty b) + | FLong a, FLong b -> FLong (op64 a b) + | _ -> failwith ("fkinds do not match. Values: " ^ show x ^ " and " ^ show y) let lift2_cmp (op32, op64) x y = match x, y with | F32 a, F32 b -> op32 a b | F64 a, F64 b -> op64 a b - | F32 a, F64 b -> failwith ("fkinds float and double are incompatible. Values: " ^ Prelude.Ana.sprint F32Interval.pretty a ^ " and " ^ Prelude.Ana.sprint F64Interval.pretty b) - | F64 a, F32 b -> failwith ("fkinds double and float are incompatible. Values: " ^ Prelude.Ana.sprint F64Interval.pretty a ^ " and " ^ Prelude.Ana.sprint F32Interval.pretty b) + | FLong a, FLong b -> op64 a b + | _ -> failwith ("fkinds do not match. Values: " ^ show x ^ " and " ^ show y) - let lift (op32, op64) x = match x with + let lift (op32, op64) = function | F32 a -> F32 (op32 a) | F64 a -> F64 (op64 a) + | FLong a -> FLong (op64 a) - let dispatch (op32, op64) x = match x with + let dispatch (op32, op64) = function | F32 a -> op32 a - | F64 a -> op64 a + | F64 a | FLong a -> op64 a let dispatch_fkind fkind (op32, op64) = match fkind with | FFloat -> F32 (op32 ()) - | FDouble -> F64 (op64 ()) - | _ -> + | FDouble -> F64 (op64 ()) + | FLongDouble -> FLong (op64 ()) + | _ -> (* this sould never be reached, as we have to check for invalid fkind elsewhere, - however we could instead of crashing also return top_of some fkind to vaid this and nonetheless have no actual information about anything*) - failwith "unsupported fkind" + however we could instead of crashing also return top_of some fkind to avoid this and nonetheless have no actual information about anything*) + failwith "unsupported fkind" let neg = lift (F1.neg, F2.neg) let acos = lift (F1.acos, F2.acos) @@ -509,7 +519,10 @@ module FloatIntervalImplLifted = struct let top () = failwith "top () is not implemented for FloatIntervalImplLifted." let is_top = dispatch (F1.is_bot, F2.is_bot) - let get_fkind = dispatch ((fun _ -> FFloat), (fun _ -> FDouble)) + let get_fkind = function + | F32 _ -> FFloat + | F64 _ -> FDouble + | FLong _ -> FLongDouble let leq = lift2_cmp (F1.leq, F2.leq) let join = lift2 (F1.join, F2.join) @@ -518,7 +531,6 @@ module FloatIntervalImplLifted = struct let narrow = lift2 (F1.narrow, F2.narrow) let is_exact = dispatch (F1.is_exact, F2.is_exact) - let show = dispatch (F1.show, F2.show) (* TODO add fkind to output *) let pretty = (fun () -> dispatch (F1.pretty (), F2.pretty ())) (* TODO add fkind to output *) let pretty_diff () (x, y) = lift2_cmp ((fun a b -> F1.pretty_diff () (a, b)), (fun a b -> F2.pretty_diff () (a, b))) x y(* TODO add fkind to output *) @@ -541,18 +553,15 @@ module FloatIntervalImplLifted = struct let minimal = dispatch (F1.minimal, F2.minimal) let maximal = dispatch (F1.maximal, F2.maximal) let to_int ikind = dispatch (F1.to_int ikind, F2.to_int ikind) - let cast_to fkind x = + let cast_to fkind = let create_interval fkind l h = match l, h with | Some l, Some h -> of_interval fkind (l,h) | Some l, None -> starting fkind l | None, Some h -> ending fkind h | _ -> top_of fkind - in - match x, fkind with - | F32 a, FDouble -> create_interval FDouble (F1.minimal a) (F1.maximal a) - | F64 a, FFloat -> create_interval FFloat (F2.minimal a) (F2.maximal a) - | _ -> x + in + dispatch ((fun a -> create_interval fkind (F1.minimal a) (F1.maximal a)), (fun a -> create_interval fkind (F2.minimal a) (F2.maximal a))) let invariant e (x:t) = let fk = get_fkind x in @@ -634,10 +643,10 @@ module FloatDomTupleImpl = struct create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.ending fkind); } let starting fkind = create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.starting fkind); } - let ending_before fkind = - create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.ending_before fkind); } - let starting_after fkind = - create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.starting_after fkind); } + let ending_before fkind = + create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.ending_before fkind); } + let starting_after fkind = + create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.starting_after fkind); } let of_string fkind = create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.of_string fkind); } diff --git a/src/cdomains/valueDomain.ml b/src/cdomains/valueDomain.ml index 046a05ae29..077f8fa335 100644 --- a/src/cdomains/valueDomain.ml +++ b/src/cdomains/valueDomain.ml @@ -139,7 +139,7 @@ struct match t with | t when is_mutex_type t -> `Top | TInt (ik,_) -> `Int (ID.top_of ik) - | TFloat (FFloat | FDouble as fkind, _) -> `Float (FD.top_of fkind) + | TFloat (fkind, _) -> `Float (FD.top_of fkind) | TPtr _ -> `Address AD.top_ptr | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> init_value fd.ftype) ci) | TComp ({cstruct=false; _},_) -> `Union (Unions.top ()) @@ -155,7 +155,7 @@ struct let rec top_value (t: typ): t = match t with | TInt (ik,_) -> `Int (ID.(cast_to ik (top_of ik))) - | TFloat (FFloat | FDouble as fkind, _) -> `Float (FD.top_of fkind) + | TFloat (fkind, _) -> `Float (FD.top_of fkind) | TPtr _ -> `Address AD.top_ptr | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> top_value fd.ftype) ci) | TComp ({cstruct=false; _},_) -> `Union (Unions.top ()) @@ -183,7 +183,7 @@ struct let rec zero_init_value (t:typ): t = match t with | TInt (ikind, _) -> `Int (ID.of_int ikind BI.zero) - | TFloat (FFloat | FDouble as fkind, _) -> `Float (FD.of_const fkind 0.0) + | TFloat (fkind, _) -> `Float (FD.of_const fkind 0.0) | TPtr _ -> `Address AD.null_ptr | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> zero_init_value fd.ftype) ci) | TComp ({cstruct=false; _} as ci,_) -> @@ -375,7 +375,7 @@ struct (match Structs.get x first with `Int x -> x | _ -> raise CastError)*) | _ -> log_top __POS__; ID.top_of ik )) - | TFloat (FFloat | FDouble as fkind,_) -> + | TFloat (fkind,_) -> (match v with |`Int ix -> `Float (FD.of_int fkind ix) |`Float fx -> `Float (FD.cast_to fkind fx) diff --git a/src/util/options.schema.json b/src/util/options.schema.json index 68d4dcfaa2..8419896acd 100644 --- a/src/util/options.schema.json +++ b/src/util/options.schema.json @@ -1583,7 +1583,7 @@ "title": "warn.float", "description": "float warnings", "type": "boolean", - "default": false + "default": true }, "cast": { "title": "warn.cast", diff --git a/tests/regression/57-floats/01-base.c b/tests/regression/57-floats/01-base.c index 0635df2512..3f4b16ff27 100644 --- a/tests/regression/57-floats/01-base.c +++ b/tests/regression/57-floats/01-base.c @@ -8,25 +8,34 @@ int main() { double x, a = 2., b = 3. + 1; float y, c = 2.f, d = 3.f + 1; + long double z, e = 2.l, f = 3.l + 1; assert(x == 2.); // UNKNOWN! assert(y == 2.f); // UNKNOWN! + assert(z == 2.l); // UNKNOWN! - assert(a == 2.); // SUCCESS! - assert(a < 10.); // SUCCESS! - assert(a > 10.); // FAIL! + assert(a == 2.); // SUCCESS + assert(a < 10.); // SUCCESS + assert(a > 10.); // FAIL - assert(c == 2.f); // SUCCESS! - assert(c < 10.f); // SUCCESS! - assert(c > 10.f); // FAIL! + assert(c == 2.f); // SUCCESS + assert(c < 10.f); // SUCCESS + assert(c > 10.f); // FAIL + + assert(e == 2.f); // SUCCESS + assert(e < 10.f); // SUCCESS + assert(e > 10.f); // FAIL x = (a + b) / 2.; // naive way of computing the middle y = (c + d) / 2.; // naive way of computing the middle + z = (e + f) / 2.; // naive way of computing the middle - assert(x == 3.); // SUCCESS! - assert(y == 3.f); // SUCCESS! + assert(x == 3.); // SUCCESS + assert(y == 3.f); // SUCCESS + assert(z == 3.f); // SUCCESS assert(-97. == x - 100.); assert(-97.f == y - 100.f); + assert(-97.f == z - 100.f); return 0; } diff --git a/tests/regression/57-floats/02-node_configuration.c b/tests/regression/57-floats/02-node_configuration.c index a05268836a..47a81d1429 100644 --- a/tests/regression/57-floats/02-node_configuration.c +++ b/tests/regression/57-floats/02-node_configuration.c @@ -7,7 +7,7 @@ void test() __attribute__((goblint_precision("float-interval"))); int main() { double a = 2.; - assert(a == 2.); // UNKNOWN! + assert(a == 2.); // UNKNOWN test(); return 0; } @@ -15,5 +15,5 @@ int main() void test() { double b = 2.; - assert(b == 2.); // SUCCESS! + assert(b == 2.); // SUCCESS } diff --git a/tests/regression/57-floats/03-infinity_or_nan.c b/tests/regression/57-floats/03-infinity_or_nan.c index bd364b168f..94cadde2b7 100644 --- a/tests/regression/57-floats/03-infinity_or_nan.c +++ b/tests/regression/57-floats/03-infinity_or_nan.c @@ -1,14 +1,22 @@ -// PARAM: --enable ana.float.interval --enable warn.float -#include +// PARAM: --enable ana.float.interval +#include void main() { double data; float data2; + long double data3; double result1 = data + 1.0; // WARN: Could be +/-infinity or Nan double result2 = data / 0.; // WARN: Could be +/-infinity or Nan double result3 = data2 + 1.0f; // WARN: Could be +/-infinity or Nan double result4 = data2 / 0.f; // WARN: Could be +/-infinity or Nan + + double result5 = data3 + 1.0l; // WARN: Could be +/-infinity or Nan + double result6 = data3 / 0.l; // WARN: Could be +/-infinity or Nan + + // even though it will likely fit into a long double, the following also + // gives a warning as our long doubles internally only have the precision of "normal" doubles + data3 = DBL_MAX + 1.l; // WARN: Could be +/-infinity or Nan } diff --git a/tests/regression/57-floats/04-casts.c b/tests/regression/57-floats/04-casts.c index fe16e1687a..30439d36d5 100644 --- a/tests/regression/57-floats/04-casts.c +++ b/tests/regression/57-floats/04-casts.c @@ -17,68 +17,93 @@ int main() double value; float value2; + long double value3; int i; long l; unsigned u; - // Casts from double into different variants of ints - assert((int)0.0); // FAIL! - assert((long)0.0); // FAIL! - assert((unsigned)0.0); // FAIL! - assert((int)0.0f); // FAIL! - assert((long)0.0f); // FAIL! - assert((unsigned)0.0f); // FAIL! - - assert((unsigned)1.0); // SUCCESS! - assert((long)2.0); // SUCCESS! - assert((int)3.0); // SUCCESS! - assert((unsigned)1.0f); // SUCCESS! - assert((long)2.0f); // SUCCESS! - assert((int)3.0f); // SUCCESS! - - // Cast from int into double - assert((double)0); // FAIL! - assert((double)0l); // FAIL! - assert((double)0u); // FAIL! - - assert((double)1u); // SUCCESS! - assert((double)2l); // SUCCESS! - assert((double)3); // SUCCESS! - - assert((float)0); // FAIL! - assert((float)0l); // FAIL! - assert((float)0u); // FAIL! - - assert((float)1u); // SUCCESS! - assert((float)2l); // SUCCESS! - assert((float)3); // SUCCESS! + // Casts from double/flout/long double into different variants of ints + assert((int)0.0); // FAIL + assert((long)0.0); // FAIL + assert((unsigned)0.0); // FAIL + assert((int)0.0f); // FAIL + assert((long)0.0f); // FAIL + assert((unsigned)0.0f); // FAIL + assert((int)0.0l); // FAIL + assert((long)0.0l); // FAIL + assert((unsigned)0.0l); // FAIL + + assert((unsigned)1.0); // SUCCESS + assert((long)2.0); // SUCCESS + assert((int)3.0); // SUCCESS + assert((unsigned)1.0f); // SUCCESS + assert((long)2.0f); // SUCCESS + assert((int)3.0f); // SUCCESS + assert((unsigned)1.0l); // SUCCESS + assert((long)2.0l); // SUCCESS + assert((int)3.0l); // SUCCESS + + // Cast from int into double/flaot/long double + assert((double)0); // FAIL + assert((double)0l); // FAIL + assert((double)0u); // FAIL + + assert((double)1u); // SUCCESS + assert((double)2l); // SUCCESS + assert((double)3); // SUCCESS + + assert((float)0); // FAIL + assert((float)0l); // FAIL + assert((float)0u); // FAIL + + assert((float)1u); // SUCCESS + assert((float)2l); // SUCCESS + assert((float)3); // SUCCESS + + assert((long double)0); // FAIL + assert((long double)0l); // FAIL + assert((long double)0u); // FAIL + + assert((long double)1u); // SUCCESS + assert((long double)2l); // SUCCESS + assert((long double)3); // SUCCESS // cast with ranges RANGE(i, -5, 5); value = (double)i; - assert(-5. <= value && value <= 5.f); // SUCCESS! + assert(-5. <= value && value <= 5.f); // SUCCESS value2 = (float)i; - assert(-5.f <= value2 && value2 <= 5.); // SUCCESS! + assert(-5.f <= value2 && value2 <= 5.); // SUCCESS + value3 = (long double)i; + assert(-5.f <= value3 && value3 <= 5.l); // SUCCESS RANGE(l, 10, 20); value = l; - assert(10.f <= value && value <= 20.); // SUCCESS! + assert(10.f <= value && value <= 20.); // SUCCESS value2 = l; - assert(10. <= value2 && value2 <= 20.f); // SUCCESS! + assert(10.l <= value2 && value2 <= 20.f); // SUCCESS + value3 = l; + assert(10. <= value2 && value2 <= 20.); // SUCCESS RANGE(u, 100, 1000); value = u; - assert(value > 1.); // SUCCESS! + assert(value > 1.); // SUCCESS value2 = u; - assert(value2 > 1.f); // SUCCESS! + assert(value2 > 1.f); // SUCCESS + value3 = u; + assert(value2 > 1.l); // SUCCESS RANGE(value, -10.f, 10.); i = (int)value; - assert(-10 <= i && i <= 10); // SUCCESS! + assert(-10 <= i && i <= 10); // SUCCESS RANGE(value2, -10.f, 10.); i = (int)value2; - assert(-10 <= i && i <= 10); // SUCCESS! + assert(-10 <= i && i <= 10); // SUCCESS + + RANGE(value3, -10.l, 10.); + i = (int)value3; + assert(-10 <= i && i <= 10); // SUCCESS return 0; } diff --git a/tests/regression/57-floats/05-invariant.c b/tests/regression/57-floats/05-invariant.c index 8abdee7aee..1b4a7dc1a9 100644 --- a/tests/regression/57-floats/05-invariant.c +++ b/tests/regression/57-floats/05-invariant.c @@ -6,93 +6,112 @@ int main() { double a; float b; - int c, d; + long double c; + int d; // make a definitly finite! - if (c) + if (d) { a = 100.; b = 100.; + c = 100.; } else { a = -100.; b = -100.; + c = -100.; }; if (a != 1.) { // this would require a exclusion list etc. - assert(a != 1.); // UNKNOWN! + assert(a != 1.); // UNKNOWN } if (a == 1.) { - assert(a == 1.); // SUCCESS! + assert(a == 1.); // SUCCESS } if (b == 1.f) { - assert(b == 1.f); // SUCCESS! + assert(b == 1.f); // SUCCESS } if (a <= 5.) { - assert(a <= 5.); // SUCCESS! + assert(a <= 5.); // SUCCESS } if (b <= 5.f) { - assert(b <= 5.f); // SUCCESS! + assert(b <= 5.f); // SUCCESS + } + if (c <= 5.f) + { + assert(c <= 5.f); // SUCCESS } if (a <= 5. && a >= -5.) { - assert(a <= 5. && a >= -5.); // SUCCESS! + assert(a <= 5. && a >= -5.); // SUCCESS } if (b <= 5.f && b >= -5.f) { - assert(b <= 5. && b >= -5.); // SUCCESS! + assert(b <= 5. && b >= -5.); // SUCCESS } if (a + 5.f < 10.f) { - assert(a <= 5.); // SUCCESS! + assert(a <= 5.); // SUCCESS } if (b + 5.f < 10.f) { - assert(b <= 5.f); // SUCCESS! + assert(b <= 5.l); // SUCCESS + } + if (c + 5.f < 10.f) + { + assert(c <= 5.f); // SUCCESS } if (a * 2. < 6.f) { - assert(a <= 3.); // SUCCESS! + assert(a <= 3.); // SUCCESS } if (b * 2.f < 6.f) { - assert(b <= 3.f); // SUCCESS! + assert(b <= 3.f); // SUCCESS + } + if (c * 2. < 6.f) + { + assert(c <= 3.f); // SUCCESS } if (a / 3. > 10.) { - assert(a >= 30.); // SUCCESS! + assert(a >= 30.); // SUCCESS } if (b / 3.f > 10.f) { - assert(b >= 30); // SUCCESS! + assert(b >= 30); // SUCCESS } if (a < 10) { - assert(a < 10.); // SUCCESS! + assert(a < 10.); // SUCCESS } if (b < 10) { - assert(b < 10.f); // SUCCESS! + assert(b < 10.f); // SUCCESS + } + if (c < 10) + { + assert(c < 10.l); // SUCCESS } if (a > 1.) { - assert(a < 1.); // FAIL! + assert(a < 1.); // FAIL if (a < 1.) { assert(0); // NOWARN diff --git a/tests/regression/57-floats/06-library_functions.c b/tests/regression/57-floats/06-library_functions.c index 442a1df7be..52c37c6704 100644 --- a/tests/regression/57-floats/06-library_functions.c +++ b/tests/regression/57-floats/06-library_functions.c @@ -1,79 +1,80 @@ -// PARAM: --enable ana.float.interval --enable ana.int.interval --enable warn.float +// PARAM: --enable ana.float.interval --enable ana.int.interval #include #include #include -int main() { +int main() +{ double dbl_min = 2.2250738585072014e-308; double inf = 1. / 0.; double nan = 0. / 0.; //__buitin_isfinite(x): - assert(__builtin_isfinite(1.0)); //SUCCESS! - assert(__builtin_isfinite(inf)); //UNKNOWN - assert(__builtin_isfinite(nan)); //UNKNOWN + assert(__builtin_isfinite(1.0)); // SUCCESS + assert(__builtin_isfinite(inf)); // UNKNOWN + assert(__builtin_isfinite(nan)); // UNKNOWN //__buitin_isinf(x): - assert(__builtin_isinf(1.0)); //FAIL! - assert(__builtin_isinf(inf)); //UNKNOWN - assert(__builtin_isinf(nan)); //UNKNOWN + assert(__builtin_isinf(1.0)); // FAIL + assert(__builtin_isinf(inf)); // UNKNOWN + assert(__builtin_isinf(nan)); // UNKNOWN //__buitin_isinf_sign(x): - assert(__builtin_isinf_sign(1.0)); //FAIL! - assert(__builtin_isinf_sign(inf)); //UNKNOWN - assert(__builtin_isinf_sign(- inf)); //UNKNOWN - assert(__builtin_isinf_sign(nan)); //UNKNOWN + assert(__builtin_isinf_sign(1.0)); // FAIL + assert(__builtin_isinf_sign(inf)); // UNKNOWN + assert(__builtin_isinf_sign(-inf)); // UNKNOWN + assert(__builtin_isinf_sign(nan)); // UNKNOWN //__buitin_isnan(x): - assert(__builtin_isnan(1.0)); //FAIL! - assert(__builtin_isnan(inf)); //UNKNOWN - assert(__builtin_isnan(nan)); //UNKNOWN + assert(__builtin_isnan(1.0)); // FAIL + assert(__builtin_isnan(inf)); // UNKNOWN + assert(__builtin_isnan(nan)); // UNKNOWN //__buitin_isnormal(x): - assert(__builtin_isnormal(dbl_min)); //SUCCESS! - assert(__builtin_isnormal(0.0)); //FAIL! - assert(__builtin_isnormal(dbl_min / 2)); //FAIL! - assert(__builtin_isnormal(inf)); //UNKNOWN - assert(__builtin_isnormal(nan)); //UNKNOWN + assert(__builtin_isnormal(dbl_min)); // SUCCESS + assert(__builtin_isnormal(0.0)); // FAIL + assert(__builtin_isnormal(dbl_min / 2)); // FAIL + assert(__builtin_isnormal(inf)); // UNKNOWN + assert(__builtin_isnormal(nan)); // UNKNOWN //__buitin_signbit(x): - assert(__builtin_signbit(1.0)); //FAIL! - assert(__builtin_signbit(-1.0)); //SUCCESS! - assert(__builtin_signbit(0.0)); //UNKNOWN - assert(__builtin_signbit(inf)); //UNKNOWN - assert(__builtin_signbit(- inf)); //UNKNOWN - assert(__builtin_signbit(nan)); //UNKNOWN + assert(__builtin_signbit(1.0)); // FAIL + assert(__builtin_signbit(-1.0)); // SUCCESS + assert(__builtin_signbit(0.0)); // UNKNOWN + assert(__builtin_signbit(inf)); // UNKNOWN + assert(__builtin_signbit(-inf)); // UNKNOWN + assert(__builtin_signbit(nan)); // UNKNOWN double greater_than_pi = 3.142; - //acos(x): - assert((0. <= acos(0.1)) && (acos(0.1) <= greater_than_pi)); //SUCCESS! - assert(acos(1.) == 0.); //SUCCESS! - acos(2.0); //WARN: Domain error will occur: acos argument is outside of [-1., 1.] + // acos(x): + assert((0. <= acos(0.1)) && (acos(0.1) <= greater_than_pi)); // SUCCESS + assert(acos(1.) == 0.); // SUCCESS + acos(2.0); // WARN: Domain error will occur: acos argument is outside of [-1., 1.] - //asin(x): - assert(((- greater_than_pi / 2.) <= asin(0.1)) && (asin(0.1) <= (greater_than_pi / 2.))); //SUCCESS! - assert(asin(0.) == 0.); //SUCCESS! - asin(2.0); //WARN: Domain error will occur: asin argument is outside of [-1., 1.] + // asin(x): + assert(((-greater_than_pi / 2.) <= asin(0.1)) && (asin(0.1) <= (greater_than_pi / 2.))); // SUCCESS + assert(asin(0.) == 0.); // SUCCESS + asin(2.0); // WARN: Domain error will occur: asin argument is outside of [-1., 1.] - //atan(x): - assert(((- greater_than_pi / 2.) <= atan(0.1)) && (atan(0.1) <= (greater_than_pi / 2.))); //SUCCESS! - assert(atan(0.) == 0.); //SUCCESS! + // atan(x): + assert(((-greater_than_pi / 2.) <= atan(0.1)) && (atan(0.1) <= (greater_than_pi / 2.))); // SUCCESS + assert(atan(0.) == 0.); // SUCCESS - //atan2(y, x) - assert(((- greater_than_pi / 2.) <= atan2(0.1, 0.2)) && (atan2(0.1, 0.2) <= (greater_than_pi / 2.))); //SUCCESS! + // atan2(y, x) + assert(((-greater_than_pi / 2.) <= atan2(0.1, 0.2)) && (atan2(0.1, 0.2) <= (greater_than_pi / 2.))); // SUCCESS - //cos(x) - assert((-1. <= cos(0.1)) && (cos(0.1) <= 1.)); //SUCCESS! - assert(cos(0.) == 1.); //SUCCESS! + // cos(x) + assert((-1. <= cos(0.1)) && (cos(0.1) <= 1.)); // SUCCESS + assert(cos(0.) == 1.); // SUCCESS - //sin(x) - assert((-1. <= sin(0.1)) && (sin(0.1) <= 1.)); //SUCCESS! - assert(sin(0.) == 0.); //SUCCESS! + // sin(x) + assert((-1. <= sin(0.1)) && (sin(0.1) <= 1.)); // SUCCESS + assert(sin(0.) == 0.); // SUCCESS - //tan(x) - assert(tan(0.) == 0.); //SUCCESS! + // tan(x) + assert(tan(0.) == 0.); // SUCCESS - //unimplemented math.h function, should not invalidate globals: - j0(0.1); //NOWARN - ldexp(0.1, 1); //NOWARN + // unimplemented math.h function, should not invalidate globals: + j0(0.1); // NOWARN + ldexp(0.1, 1); // NOWARN } diff --git a/tests/regression/57-floats/07-equality.c b/tests/regression/57-floats/07-equality.c index 7139388c92..7460fad23e 100644 --- a/tests/regression/57-floats/07-equality.c +++ b/tests/regression/57-floats/07-equality.c @@ -1,9 +1,9 @@ -// PARAM: --enable ana.float.interval --enable warn.float +// PARAM: --enable ana.float.interval void main() { - int check1 = (0.2 == 0.2); // WARN - int check2 = (0.2 != 0.3); // WARN + int check1 = (0.2f == 0.2); // WARN + int check2 = (0.2 != 0.3l); // WARN // Not all integers that are this big are representable in doubles anymore... double high_value = 179769313486231568384.0; diff --git a/tests/regression/57-floats/08-bit_casts.c b/tests/regression/57-floats/08-bit_casts.c index 4e08900436..5d88b96b79 100644 --- a/tests/regression/57-floats/08-bit_casts.c +++ b/tests/regression/57-floats/08-bit_casts.c @@ -1,4 +1,4 @@ -// PARAM: --enable ana.float.interval --enable warn.float +// PARAM: --enable ana.float.interval typedef union { diff --git a/tests/regression/57-floats/09-svcomp_float_req_bl_1252b.c b/tests/regression/57-floats/09-svcomp_float_req_bl_1252b.c index 9e2030965c..8fae031bea 100644 --- a/tests/regression/57-floats/09-svcomp_float_req_bl_1252b.c +++ b/tests/regression/57-floats/09-svcomp_float_req_bl_1252b.c @@ -1,4 +1,4 @@ -// PARAM: --enable ana.float.interval --enable warn.float +// PARAM: --enable ana.float.interval typedef int __int32_t; typedef unsigned int __uint32_t; From de7aa3da85ab9ea3009c0e475f1fa91fc533864e Mon Sep 17 00:00:00 2001 From: FelixKrayer Date: Sat, 2 Jul 2022 16:03:50 +0200 Subject: [PATCH 31/54] float bot at 'intervalImpl'-level --- src/cdomains/floatDomain.ml | 203 ++++++++++++++++++++--------------- src/cdomains/floatDomain.mli | 2 + 2 files changed, 116 insertions(+), 89 deletions(-) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index fca518483c..2cf5d169a0 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -3,6 +3,8 @@ open PrecisionUtil open FloatOps open Cil +exception ArithmeticOnFloatBot of string + module type FloatArith = sig type t @@ -83,15 +85,16 @@ end module FloatIntervalImpl(Float_t : CFloatType) = struct include Printable.Std (* for default invariant, tag and relift *) - type t = (Float_t.t * Float_t.t) option [@@deriving eq, ord, to_yojson, hash] + type t = Top | Bot | Interval of (Float_t.t * Float_t.t) [@@deriving eq, ord, to_yojson, hash] let to_int ik = function - | None -> IntDomain.IntDomTuple.top_of ik + | Bot -> IntDomain.IntDomTuple.bot_of ik (**shold it rather throw ArithmeticOnFloatBot ?*) + | Top -> IntDomain.IntDomTuple.top_of ik (* special treatment for booleans as those aren't "just" truncated *) - | Some (l, h) when ik = IBool && (l > Float_t.zero || h < Float_t.zero) -> IntDomain.IntDomTuple.of_bool IBool true - | Some (l, h) when ik = IBool && l = h && l = Float_t.zero -> IntDomain.IntDomTuple.of_bool IBool false - | Some (l, h) when ik = IBool -> IntDomain.IntDomTuple.top_of IBool - | Some (l, h) -> + | Interval (l, h) when ik = IBool && (l > Float_t.zero || h < Float_t.zero) -> IntDomain.IntDomTuple.of_bool IBool true + | Interval (l, h) when ik = IBool && l = h && l = Float_t.zero -> IntDomain.IntDomTuple.of_bool IBool false + | Interval (l, h) when ik = IBool -> IntDomain.IntDomTuple.top_of IBool + | Interval (l, h) -> (* as converting from float to integer is (exactly) defined as leaving out the fractional part, (value is truncated towrad zero) we do not require specific rounding here *) IntDomain.IntDomTuple.of_interval ik (Float_t.to_big_int l, Float_t.to_big_int h) @@ -102,14 +105,15 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let l' = Float_t.of_float Down (Big_int_Z.float_of_big_int l) in let h' = Float_t.of_float Up (Big_int_Z.float_of_big_int h) in if not (Float_t.is_finite l' && Float_t.is_finite h') then - None + Top else - Some (l', h') - | _, _ -> None + Interval (l', h') + | _, _ -> Top let show = function - | None -> "[Top]" - | Some (low, high) -> Printf.sprintf "[%s,%s]" (Float_t.to_string low) (Float_t.to_string high) + | Top -> Float_t.name ^ ": [Top]" + | Bot -> Float_t.name ^ ": [Bot]" + | Interval (low, high) -> Printf.sprintf "%s:[%s,%s]" Float_t.name (Float_t.to_string low) (Float_t.to_string high) let pretty () x = text (show x) @@ -122,44 +126,40 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let pretty_diff () (x, y) = Pretty.dprintf "%a instead of %a" pretty x pretty y - let bot () = failwith "no bot exists" + let bot () = Bot - let is_bot _ = false + let is_bot = function + | Bot -> true + | _ -> false - let top () = None + let top () = Top - let is_top = Option.is_none + let is_top = function + | Top -> true + | _ -> false - let neg = Option.map (fun (low, high) -> (Float_t.neg high, Float_t.neg low)) + let neg = function + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "neg %s" (show Bot))) + | Interval (low, high) -> Interval (Float_t.neg high, Float_t.neg low) + | _ -> Top let norm v = let normed = match v with - | Some (low, high) -> + | Interval (low, high) -> if Float_t.is_finite low && Float_t.is_finite high then if low > high then failwith "invalid Interval" else v - else None - | _ -> None + else Top + | tb -> tb in if is_top normed then Messages.warn ~category:Messages.Category.Float ~tags:[CWE 189; CWE 739] "Float could be +/-infinity or Nan"; normed - (**just for norming the arbitraries, so correct intervals get created, but no failwith if low > high*) - let norm_arb v = - match v with - | Some (f1, f2) -> - let f1' = Float_t.of_float Nearest f1 in - let f2' = Float_t.of_float Nearest f2 in - if Float_t.is_finite f1' && Float_t.is_finite f2' - then Some(min f1' f2', max f1' f2') - else None - | _ -> None - (**for QCheck: should describe how to generate random values and shrink possible counter examples *) - let arbitrary () = QCheck.map norm_arb (QCheck.option (QCheck.pair QCheck.float QCheck.float)) + let arbitrary () = failwith "not implemented" - let of_interval' interval = norm @@ Some interval + let of_interval' interval = norm @@ Interval interval let of_interval (l, h) = of_interval' (Float_t.of_float Down (min l h), Float_t.of_float Up (max l h)) let of_string s = of_interval' (Float_t.atof Down s, Float_t.atof Up s) let of_const f = of_interval (f, f) @@ -169,56 +169,70 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let starting s = of_interval' (Float_t.of_float Down s, Float_t.upper_bound) let starting_after s = of_interval' (Float_t.succ @@ Float_t.of_float Down s, Float_t.upper_bound) - let minimal x = Option.bind x (fun (l, _) -> Float_t.to_float l) - let maximal x = Option.bind x (fun (_, h) -> Float_t.to_float h) + let minimal = function + | Interval (l, _) -> Float_t.to_float l + | _ -> None + + let maximal = function + | Interval (_, h) -> Float_t.to_float h + | _ -> None - let is_exact = function Some (l, v) -> l = v | _ -> false + let is_exact = function + | Interval (l, v) -> l = v + | _ -> false let leq v1 v2 = match v1, v2 with - | _, None -> true - | None, Some _ -> false - | Some (l1, h1), Some (l2, h2) -> l1 >= l2 && h1 <= h2 + | _, Top -> true + | Top, _ -> false + | Bot, _ -> true + | _, Bot -> false + | Interval (l1, h1), Interval (l2, h2) -> l1 >= l2 && h1 <= h2 let join v1 v2 = match v1, v2 with - | None, _ | _, None -> None - | Some (l1, h1), Some (l2, h2) -> Some (min l1 l2, max h1 h2) + | Top, _ | _, Top -> Top + | Bot, v | v, Bot -> v + | Interval (l1, h1), Interval (l2, h2) -> Interval (min l1 l2, max h1 h2) let meet v1 v2 = match v1, v2 with - | None, v | v, None -> v - | Some (l1, h1), Some (l2, h2) -> + | Bot, _ | _, Bot -> Bot + | Top, v | v, Top -> v + | Interval (l1, h1), Interval (l2, h2) -> let (l, h) = (max l1 l2, min h1 h2) in if l <= h - then Some (l, h) - else failwith "meet results in empty Interval" + then Interval (l, h) + else Bot (** [widen x y] assumes [leq x y]. Solvers guarantee this by calling [widen old (join old new)]. *) let widen v1 v2 = (**TODO: support 'threshold_widening' option *) match v1, v2 with - | None, _ | _, None -> None - | Some (l1, h1), Some (l2, h2) -> + | Top, _ | _, Top -> Top + | Bot, v | v, Bot -> v + | Interval (l1, h1), Interval (l2, h2) -> (**If we widen and we know that neither interval contains +-inf or nan, it is ok to widen only to +-max_float, because a widening with +-inf/nan will always result in the case above -> Top *) let low = if l1 <= l2 then l1 else Float_t.lower_bound in let high = if h1 >= h2 then h1 else Float_t.upper_bound in - norm @@ Some (low, high) + norm @@ Interval (low, high) let narrow v1 v2 = match v1, v2 with (**we cannot distinguish between the lower bound beeing -inf or the upper bound beeing inf. Also there is nan *) - | None, _ -> v2 + | Bot, _ | _, Bot -> Bot + | Top, _ -> v2 | _, _ -> v1 (** evaluation of the binary operations *) let eval_binop eval_operation op1 op2 = norm @@ match (op1, op2) with - | Some v1, Some v2 -> + | Bot, _ | _, Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "%s op %s" (show op1) (show op2))) + | Interval v1, Interval v2 -> let is_exact (lower, upper) = (lower = upper) in let is_exact_before = is_exact v1 && is_exact v2 in let result = eval_operation v1 v2 in (match result with - | Some (r1, r2) -> + | Interval (r1, r2) -> let is_exact_after = is_exact (r1, r2) in if not is_exact_after && is_exact_before then Messages.warn @@ -226,23 +240,24 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct ~tags:[CWE 197; CWE 681; CWE 1339] "The result of this operation is not exact, even though the inputs were exact."; result - | _ -> None) - | _ -> None + | bt -> bt) + | _ -> Top let eval_int_binop eval_operation (op1: t) op2 = let a, b = match (op1, op2) with - | Some v1, Some v2 -> eval_operation v1 v2 + | Bot, _ | _, Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "%s op %s" (show op1) (show op2))) + | Interval v1, Interval v2 -> eval_operation v1 v2 | _ -> (0, 1) in IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) let eval_add (l1, h1) (l2, h2) = - Some (Float_t.add Down l1 l2, Float_t.add Up h1 h2) + Interval (Float_t.add Down l1 l2, Float_t.add Up h1 h2) let eval_sub (l1, h1) (l2, h2) = - Some (Float_t.sub Down l1 h2, Float_t.sub Up h1 l2) + Interval (Float_t.sub Down l1 h2, Float_t.sub Up h1 l2) let eval_mul (l1, h1) (l2, h2) = let mul1u = Float_t.mul Up l1 l2 in @@ -255,10 +270,10 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let mul4d = Float_t.mul Down h1 h2 in let high = max (max (max mul1u mul2u) mul3u) mul4u in let low = min (min (min mul1d mul2d) mul3d) mul4d in - Some (low, high) + Interval (low, high) let eval_div (l1, h1) (l2, h2) = - if l2 <= Float_t.zero && h2 >= Float_t.zero then None + if l2 <= Float_t.zero && h2 >= Float_t.zero then Top else let div1u = Float_t.div Up l1 l2 in let div2u = Float_t.div Up l1 h2 in @@ -270,7 +285,7 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let div4d = Float_t.div Down h1 h2 in let high = max (max (max div1u div2u) div3u) div4u in let low = min (min (min div1d div2d) div3d) div4d in - Some (low, high) + Interval (low, high) let eval_lt (l1, h1) (l2, h2) = @@ -336,79 +351,89 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let unknown_IInt = IntDomain.IntDomTuple.top_of IInt let isfinite op = - match op with - | Some v -> true_nonZero_IInt - | None -> unknown_IInt + match op with + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "unop %s" (show op))) + | Interval v -> true_nonZero_IInt + | Top -> unknown_IInt let isinf op = - match op with - | Some v -> false_zero_IInt - | None -> unknown_IInt + match op with + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "unop %s" (show op))) + | Interval v -> false_zero_IInt + | Top -> unknown_IInt let isnan = isinf (**currently we cannot decide if we are NaN or +-inf; both are only in Top*) let isnormal op = - match op with - | Some (l, h) -> + match op with + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) + | Interval (l, h) -> if l >= Float_t.smallest || h <= (Float_t.neg (Float_t.smallest)) then true_nonZero_IInt else if l > (Float_t.neg (Float_t.smallest)) && h < Float_t.smallest then false_zero_IInt else unknown_IInt - | None -> unknown_IInt + | Top -> unknown_IInt (**it seems strange not to return a explicit 1 for negative numbers, but in c99 signbit is defined as: *) (**<> *) let signbit op = match op with - | Some (_, h) when h < Float_t.zero -> true_nonZero_IInt - | Some (l, _) when l > Float_t.zero -> false_zero_IInt - | Some _ -> unknown_IInt (**any interval containing zero has to fall in this case, because we do not distinguish between 0. and -0. *) - | None -> unknown_IInt + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) + | Interval (_, h) when h < Float_t.zero -> true_nonZero_IInt + | Interval (l, _) when l > Float_t.zero -> false_zero_IInt + | Interval _ -> unknown_IInt (**any interval containing zero has to fall in this case, because we do not distinguish between 0. and -0. *) + | Top -> unknown_IInt (**This Constant overapproximates pi to use as bounds for the return values of trigonometric functions *) let overapprox_pi = 3.1416 let acos op = match op with - | Some (l, _) when (is_exact op) && l = Float_t.of_float Nearest 1. -> of_const 0. (*acos(1) = 0*) - | Some (l, h) -> + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) + | Interval (l, _) when (is_exact op) && l = Float_t.of_float Nearest 1. -> of_const 0. (*acos(1) = 0*) + | Interval (l, h) -> if l < (Float_t.of_float Down (-.1.)) || h > (Float_t.of_float Up 1.) then Messages.warn ~category:Messages.Category.Float "Domain error will occur: acos argument is outside of [-1., 1.]"; of_interval (0., (overapprox_pi)) (**could be more exact *) - | None -> top () + | Top -> top () let asin op = match op with - | Some (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*asin(0) = 0*) - | Some (l, h) -> + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) + | Interval (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*asin(0) = 0*) + | Interval (l, h) -> if l < (Float_t.of_float Down (-.1.)) || h > (Float_t.of_float Up 1.) then Messages.warn ~category:Messages.Category.Float "Domain error will occur: asin argument is outside of [-1., 1.]"; div (of_interval ((-. overapprox_pi), overapprox_pi)) (of_const 2.) (**could be more exact *) - | None -> top () + | Top -> top () let atan op = match op with - | Some (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*atan(0) = 0*) - | Some _ -> div (of_interval ((-. overapprox_pi), overapprox_pi)) (of_const 2.) (**could be more exact *) - | None -> top () + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) + | Interval (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*atan(0) = 0*) + | Interval _ -> div (of_interval ((-. overapprox_pi), overapprox_pi)) (of_const 2.) (**could be more exact *) + | Top -> top () let cos op = - match op with - | Some (l, _) when (is_exact op) && l = Float_t.zero -> of_const 1. (*cos(0) = 1*) - | Some _ -> of_interval (-. 1., 1.) (**could be exact for intervals where l=h, or even for some intervals *) - | None -> top () + match op with + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) + | Interval (l, _) when (is_exact op) && l = Float_t.zero -> of_const 1. (*cos(0) = 1*) + | Interval _ -> of_interval (-. 1., 1.) (**could be exact for intervals where l=h, or even for Interval intervals *) + | Top -> top () let sin op = - match op with - | Some (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*sin(0) = 0*) - | Some _ -> of_interval (-. 1., 1.) (**could be exact for intervals where l=h, or even for some intervals *) - | None -> top () + match op with + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) + | Interval (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*sin(0) = 0*) + | Interval _ -> of_interval (-. 1., 1.) (**could be exact for intervals where l=h, or even for some intervals *) + | Top -> top () let tan op = match op with - | Some (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*tan(0) = 0*) + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) + | Interval (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*tan(0) = 0*) | _ -> top () (**could be exact for intervals where l=h, or even for some intervals *) end diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index 6c6313101d..ab9da48626 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -1,6 +1,8 @@ (** Abstract Domains for floats. These are domains that support the C * operations on double/float values. *) +exception ArithmeticOnFloatBot of string + module type FloatArith = sig type t From 577c353e0a773ce48e8bef410dc5dab587719b69 Mon Sep 17 00:00:00 2001 From: FelixKrayer Date: Mon, 4 Jul 2022 19:07:33 +0200 Subject: [PATCH 32/54] implement try with, arbitrary, suggestions --- src/analyses/base.ml | 5 +- src/cdomains/floatDomain.ml | 166 +++++++++++++-------------- unittest/cdomains/floatDomainTest.ml | 2 +- 3 files changed, 87 insertions(+), 86 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 2845c9be28..ede8e89503 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -1830,7 +1830,10 @@ struct in let inv_bin_float (a, b) c op = let open Stdlib in - let meet_bin a' b' = FD.meet a a', FD.meet b b' in + let meet_bin a' b' = + try + FD.meet a a', FD.meet b b' + with FloatDomain.ArithmeticOnFloatBot _ -> raise Deadcode in match op with | PlusA -> (* A + B = C, \forall a \in A. a + b_min > pred c_min \land a + b_max < succ c_max diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 2cf5d169a0..afe810fabf 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -87,8 +87,24 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct include Printable.Std (* for default invariant, tag and relift *) type t = Top | Bot | Interval of (Float_t.t * Float_t.t) [@@deriving eq, ord, to_yojson, hash] + let show = function + | Top -> Float_t.name ^ ": [Top]" + | Bot -> Float_t.name ^ ": [Bot]" + | Interval (low, high) -> Printf.sprintf "%s:[%s,%s]" Float_t.name (Float_t.to_string low) (Float_t.to_string high) + + let pretty () x = text (show x) + + let printXml f (x : t) = + BatPrintf.fprintf f "\n\n%s\n\n\n" (show x) + + let name () = "FloatInterval" + + (** If [leq x y = false], then [pretty_diff () (x, y)] should explain why. *) + let pretty_diff () (x, y) = + Pretty.dprintf "%a instead of %a" pretty x pretty y + let to_int ik = function - | Bot -> IntDomain.IntDomTuple.bot_of ik (**shold it rather throw ArithmeticOnFloatBot ?*) + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "to_int %s" (show Bot))) | Top -> IntDomain.IntDomTuple.top_of ik (* special treatment for booleans as those aren't "just" truncated *) | Interval (l, h) when ik = IBool && (l > Float_t.zero || h < Float_t.zero) -> IntDomain.IntDomTuple.of_bool IBool true @@ -110,27 +126,11 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct Interval (l', h') | _, _ -> Top - let show = function - | Top -> Float_t.name ^ ": [Top]" - | Bot -> Float_t.name ^ ": [Bot]" - | Interval (low, high) -> Printf.sprintf "%s:[%s,%s]" Float_t.name (Float_t.to_string low) (Float_t.to_string high) - - let pretty () x = text (show x) - - let printXml f (x : t) = - BatPrintf.fprintf f "\n\n%s\n\n\n" (show x) - - let name () = "FloatInterval" - - (** If [leq x y = false], then [pretty_diff () (x, y)] should explain why. *) - let pretty_diff () (x, y) = - Pretty.dprintf "%a instead of %a" pretty x pretty y - let bot () = Bot let is_bot = function - | Bot -> true - | _ -> false + | Bot -> true + | _ -> false let top () = Top @@ -156,8 +156,19 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct "Float could be +/-infinity or Nan"; normed + (**converts "(Float_t.t * Float_t.t) option" arbitraries to "Top | Bot | Interval of (Float_t.t * Float_t.t)". Does not create Bot*) + let convert_arb v = + match v with + | Some (f1, f2) -> + let f1' = Float_t.of_float Nearest f1 in + let f2' = Float_t.of_float Nearest f2 in + if Float_t.is_finite f1' && Float_t.is_finite f2' + then Interval (min f1' f2', max f1' f2') + else Top + | _ -> Top + (**for QCheck: should describe how to generate random values and shrink possible counter examples *) - let arbitrary () = failwith "not implemented" + let arbitrary () = QCheck.map convert_arb (QCheck.option (QCheck.pair QCheck.float QCheck.float)) let of_interval' interval = norm @@ Interval interval let of_interval (l, h) = of_interval' (Float_t.of_float Down (min l h), Float_t.of_float Up (max l h)) @@ -183,11 +194,11 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let leq v1 v2 = match v1, v2 with - | _, Top -> true - | Top, _ -> false - | Bot, _ -> true - | _, Bot -> false - | Interval (l1, h1), Interval (l2, h2) -> l1 >= l2 && h1 <= h2 + | _, Top -> true + | Top, _ -> false + | Bot, _ -> true + | _, Bot -> false + | Interval (l1, h1), Interval (l2, h2) -> l1 >= l2 && h1 <= h2 let join v1 v2 = match v1, v2 with @@ -346,96 +357,83 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let ne = eval_int_binop eval_ne - let true_nonZero_IInt = IntDomain.IntDomTuple.of_excl_list IInt [(Big_int_Z.big_int_of_int 0)] - let false_zero_IInt = IntDomain.IntDomTuple.of_int IInt (Big_int_Z.big_int_of_int 0) - let unknown_IInt = IntDomain.IntDomTuple.top_of IInt - let isfinite op = + let eval_unop onTop eval_operation op = match op with | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "unop %s" (show op))) - | Interval v -> true_nonZero_IInt - | Top -> unknown_IInt + | Interval v -> eval_operation v + | Top -> onTop - let isinf op = - match op with - | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "unop %s" (show op))) - | Interval v -> false_zero_IInt - | Top -> unknown_IInt + let true_nonZero_IInt = IntDomain.IntDomTuple.of_excl_list IInt [(Big_int_Z.big_int_of_int 0)] + let false_zero_IInt = IntDomain.IntDomTuple.of_int IInt (Big_int_Z.big_int_of_int 0) + let unknown_IInt = IntDomain.IntDomTuple.top_of IInt - let isnan = isinf (**currently we cannot decide if we are NaN or +-inf; both are only in Top*) + let eval_isfinite _ = true_nonZero_IInt - let isnormal op = - match op with - | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) - | Interval (l, h) -> + let eval_isinf _ = false_zero_IInt + + let eval_isnormal = function + | (l, h) -> if l >= Float_t.smallest || h <= (Float_t.neg (Float_t.smallest)) then true_nonZero_IInt else if l > (Float_t.neg (Float_t.smallest)) && h < Float_t.smallest then false_zero_IInt else unknown_IInt - | Top -> unknown_IInt - (**it seems strange not to return a explicit 1 for negative numbers, but in c99 signbit is defined as: *) + (**it seems strange not to return an explicit 1 for negative numbers, but in c99 signbit is defined as: *) (**<> *) - let signbit op = - match op with - | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) - | Interval (_, h) when h < Float_t.zero -> true_nonZero_IInt - | Interval (l, _) when l > Float_t.zero -> false_zero_IInt - | Interval _ -> unknown_IInt (**any interval containing zero has to fall in this case, because we do not distinguish between 0. and -0. *) - | Top -> unknown_IInt + let eval_signbit = function + | (_, h) when h < Float_t.zero -> true_nonZero_IInt + | (l, _) when l > Float_t.zero -> false_zero_IInt + | _ -> unknown_IInt (**any interval containing zero has to fall in this case, because we do not distinguish between 0. and -0. *) (**This Constant overapproximates pi to use as bounds for the return values of trigonometric functions *) let overapprox_pi = 3.1416 - let acos op = - match op with - | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) - | Interval (l, _) when (is_exact op) && l = Float_t.of_float Nearest 1. -> of_const 0. (*acos(1) = 0*) - | Interval (l, h) -> + let eval_acos = function + | (l, h) when l = h && l = Float_t.of_float Nearest 1. -> of_const 0. (*acos(1) = 0*) + | (l, h) -> if l < (Float_t.of_float Down (-.1.)) || h > (Float_t.of_float Up 1.) then Messages.warn ~category:Messages.Category.Float "Domain error will occur: acos argument is outside of [-1., 1.]"; of_interval (0., (overapprox_pi)) (**could be more exact *) - | Top -> top () - let asin op = - match op with - | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) - | Interval (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*asin(0) = 0*) - | Interval (l, h) -> + let eval_asin = function + | (l, h) when l = h && l = Float_t.zero -> of_const 0. (*asin(0) = 0*) + | (l, h) -> if l < (Float_t.of_float Down (-.1.)) || h > (Float_t.of_float Up 1.) then Messages.warn ~category:Messages.Category.Float "Domain error will occur: asin argument is outside of [-1., 1.]"; div (of_interval ((-. overapprox_pi), overapprox_pi)) (of_const 2.) (**could be more exact *) - | Top -> top () - let atan op = - match op with - | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) - | Interval (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*atan(0) = 0*) - | Interval _ -> div (of_interval ((-. overapprox_pi), overapprox_pi)) (of_const 2.) (**could be more exact *) - | Top -> top () + let eval_atan = function + | (l, h) when l = h && l = Float_t.zero -> of_const 0. (*atan(0) = 0*) + | _ -> div (of_interval ((-. overapprox_pi), overapprox_pi)) (of_const 2.) (**could be more exact *) - let cos op = - match op with - | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) - | Interval (l, _) when (is_exact op) && l = Float_t.zero -> of_const 1. (*cos(0) = 1*) - | Interval _ -> of_interval (-. 1., 1.) (**could be exact for intervals where l=h, or even for Interval intervals *) - | Top -> top () + let eval_cos = function + | (l, h) when l = h && l = Float_t.zero -> of_const 1. (*cos(0) = 1*) + | _ -> of_interval (-. 1., 1.) (**could be exact for intervals where l=h, or even for Interval intervals *) - let sin op = - match op with - | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) - | Interval (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*sin(0) = 0*) - | Interval _ -> of_interval (-. 1., 1.) (**could be exact for intervals where l=h, or even for some intervals *) - | Top -> top () + let eval_sin = function + | (l, h) when l = h && l = Float_t.zero -> of_const 0. (*sin(0) = 0*) + | _ -> of_interval (-. 1., 1.) (**could be exact for intervals where l=h, or even for some intervals *) - let tan op = - match op with - | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "isfinite %s" (show op))) - | Interval (l, _) when (is_exact op) && l = Float_t.zero -> of_const 0. (*tan(0) = 0*) + let eval_tan = function + | (l, h) when l = h && l = Float_t.zero -> of_const 0. (*tan(0) = 0*) | _ -> top () (**could be exact for intervals where l=h, or even for some intervals *) + let isfinite = eval_unop unknown_IInt eval_isfinite + let isinf = eval_unop unknown_IInt eval_isinf + let isnan = isinf (**currently we cannot decide if we are NaN or +-inf; both are only in Top*) + let isnormal = eval_unop unknown_IInt eval_isnormal + let signbit = eval_unop unknown_IInt eval_signbit + + let acos = eval_unop (top ()) eval_acos + let asin = eval_unop (top ()) eval_asin + let atan = eval_unop (top ()) eval_atan + let cos = eval_unop (top ()) eval_cos + let sin = eval_unop (top ()) eval_sin + let tan = eval_unop (top ()) eval_tan + end module F64Interval = FloatIntervalImpl(CDouble) diff --git a/unittest/cdomains/floatDomainTest.ml b/unittest/cdomains/floatDomainTest.ml index 249a475009..cba30e6ae8 100644 --- a/unittest/cdomains/floatDomainTest.ml +++ b/unittest/cdomains/floatDomainTest.ml @@ -189,7 +189,7 @@ struct begin check_meet (FI.top ()) (FI.top ()) (FI.top ()); check_meet (FI.top ()) fi_one fi_one; - assert_raises (Failure "meet results in empty Interval") (fun () -> (FI.meet fi_zero fi_one)); + check_meet fi_zero fi_one (FI.bot ()); check_meet (FI.of_interval (0., 10.)) (FI.of_interval (5., 20.)) (FI.of_interval (5., 10.)); end From 14cf19d3a5f60c681c1d96062e521cb2745c006a Mon Sep 17 00:00:00 2001 From: FelixKrayer Date: Tue, 5 Jul 2022 14:19:20 +0200 Subject: [PATCH 33/54] add svcomp test and unittest for exception --- src/analyses/base.ml | 231 +++++++++--------- src/cdomains/floatDomain.ml | 43 ++-- ...10-svcomp_floats_cbmc_regression_float11.c | 49 ++++ unittest/cdomains/floatDomainTest.ml | 10 + 4 files changed, 193 insertions(+), 140 deletions(-) create mode 100644 tests/regression/57-floats/10-svcomp_floats_cbmc_regression_float11.c diff --git a/src/analyses/base.ml b/src/analyses/base.ml index ede8e89503..297b0d106c 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -1830,122 +1830,121 @@ struct in let inv_bin_float (a, b) c op = let open Stdlib in - let meet_bin a' b' = - try - FD.meet a a', FD.meet b b' - with FloatDomain.ArithmeticOnFloatBot _ -> raise Deadcode in - match op with - | PlusA -> - (* A + B = C, \forall a \in A. a + b_min > pred c_min \land a + b_max < succ c_max - \land a + b_max > pred c_min \land a + b_min < succ c_max - \rightarrow A = [min(pred c_min - b_min, pred c_min - b_max), max(succ c_max - b_max, succ c_max - b_min)] - \rightarrow A = [pred c_min - b_max, succ c_max - b_min] - *) - let reverse_add v v' = (match FD.minimal c, FD.maximal c, FD.minimal v, FD.maximal v with - | Some c_min, Some c_max, Some v_min, Some v_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> - let l = Float.pred c_min -. v_max in - let h = Float.succ c_max -. v_min in - FD.of_interval (FD.get_fkind c) (l, h) - | _ -> v') in - meet_bin (reverse_add b a) (reverse_add a b) - | MinusA -> - (* A - B = C \ forall a \in A. a - b_max > pred c_min \land a - b_min < succ c_max - \land a - b_min > pred c_min \land a - b_max < succ c_max - \rightarrow A = [min(pred c_min + b_max, pred c_min + b_min), max(succ c_max + b_max, succ c_max + b_max)] - \rightarrow A = [pred c_min + b_min, succ c_max + b_max] - *) - let a' = (match FD.minimal c, FD.maximal c, FD.minimal b, FD.maximal b with - | Some c_min, Some c_max, Some b_min, Some b_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> - let l = Float.pred c_min +. b_min in - let h = Float.succ c_max +. b_max in - FD.of_interval (FD.get_fkind c) (l, h) - | _ -> a) in - (* A - B = C \ forall b \in B. a_min - b > pred c_min \land a_max - b < succ c_max - \land a_max - b > pred c_min \land a_min - b < succ c_max - \rightarrow B = [min(a_max - succ c_max, a_min - succ c_max), max(a_min - pred c_min, a_max - pred c_min)] - \rightarrow B = [a_min - succ c_max, a_max - pred c_min] - *) - let b' = (match FD.minimal c, FD.maximal c, FD.minimal a, FD.maximal a with - | Some c_min, Some c_max, Some a_min, Some a_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> - let l = a_min -. Float.succ c_max in - let h = a_max -. Float.pred c_min in - FD.of_interval (FD.get_fkind c) (l, h) - | _ -> b) in - meet_bin a' b' - | Mult -> - (* A * B = C \forall a \in A, a > 0. a * b_min > pred c_min \land a * b_max < succ c_max - A * B = C \forall a \in A, a < 0. a * b_max > pred c_min \land a * b_min < succ c_max - (with negative b reversed <>) - \rightarrow A = [min(pred c_min / b_min, pred c_min / b_max, succ c_max / b_min, succ c_max /b_max), - max(succ c_max / b_min, succ c_max /b_max, pred c_min / b_min, pred c_min / b_max)] - *) - let reverse_mul v v' = (match FD.minimal c, FD.maximal c, FD.minimal v, FD.maximal v with - | Some c_min, Some c_max, Some v_min, Some v_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> - let v1, v2, v3, v4 = (Float.pred c_min /. v_min), (Float.pred c_min /. v_max), (Float.succ c_max /. v_min), (Float.succ c_max /. v_max) in - let l = Float.min (Float.min v1 v2) (Float.min v3 v4) in - let h = Float.max (Float.max v1 v2) (Float.max v3 v4) in - FD.of_interval (FD.get_fkind c) (l, h) - | _ -> v') in - meet_bin (reverse_mul b a) (reverse_mul a b) - | Div -> - (* A / B = C \forall a \in A, a > 0, b_min > 1. a / b_max > pred c_min \land a / b_min < succ c_max - A / B = C \forall a \in A, a < 0, b_min > 1. a / b_min > pred c_min \land a / b_max < succ c_max - A / B = C \forall a \in A, a > 0, 0 < b_min, b_max < 1. a / b_max > pred c_min \land a / b_min < succ c_max - A / B = C \forall a \in A, a < 0, 0 < b_min, b_max < 1. a / b_min > pred c_min \land a / b_max < succ c_max - ... same for negative b - \rightarrow A = [min(b_max * pred c_min, b_min * pred c_min, b_min * succ c_max, b_max * succ c_max), - max(b_max * succ c_max, b_min * succ c_max, b_max * pred c_min, b_min * pred c_min)] - *) - let a' = (match FD.minimal c, FD.maximal c, FD.minimal b, FD.maximal b with - | Some c_min, Some c_max, Some b_min, Some b_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> - let v1, v2, v3, v4 = (Float.pred c_min *. b_max), (Float.pred c_min *. b_min), (Float.succ c_max *. b_max), (Float.succ c_max *. b_min) in - let l = Float.min (Float.min v1 v2) (Float.min v3 v4) in - let h = Float.max (Float.max v1 v2) (Float.max v3 v4) in - FD.of_interval (FD.get_fkind c) (l, h) - | _ -> a) in - (* A / B = C \forall b \in B, b > 0, a_min / b > pred c_min \land a_min / b < succ c_max - \land a_max / b > pred c_min \land a_max / b < succ c_max - A / B = C \forall b \in B, b < 0, a_min / b > pred c_min \land a_min / b < succ c_max - \land a_max / b > pred c_min \land a_max / b < succ c_max - \rightarrow (b != 0) B = [min(a_min / succ c_max, a_max / succ c_max, a_min / pred c_min, a_max / pred c_min), - max(a_min / pred c_min, a_max / pred c_min, a_min / succ c_max, a_max / succ c_max)] - *) - let b' = (match FD.minimal c, FD.maximal c, FD.minimal a, FD.maximal a with - | Some c_min, Some c_max, Some a_min, Some a_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> - let v1, v2, v3, v4 = (a_min /. Float.pred c_min), (a_max /. Float.pred c_min), (a_min /. Float.succ c_max), (a_max /. Float.succ c_max) in - let l = Float.min (Float.min v1 v2) (Float.min v3 v4) in - let h = Float.max (Float.max v1 v2) (Float.max v3 v4) in - FD.of_interval (FD.get_fkind c) (l, h) - | _ -> b) in - meet_bin a' b' - | Eq | Ne as op -> - let both x = x, x in - (match op, ID.to_bool (FD.to_int IBool c) with - | Eq, Some true - | Ne, Some false -> both (FD.meet a b) (* def. equal: if they compare equal, both values must be from the meet *) - | Eq, Some false - | Ne, Some true -> (* def. unequal *) - (* M.debug ~category:Analyzer "Can't use uneqal information about float value in expression \"%a\"." d_plainexp exp; *) - a, b - | _, _ -> a, b - ) - | Lt | Le | Ge | Gt as op -> - (match FD.minimal a, FD.maximal a, FD.minimal b, FD.maximal b with - | Some l1, Some u1, Some l2, Some u2 -> - (match op, ID.to_bool (FD.to_int IBool c) with - | Le, Some true - | Gt, Some false -> meet_bin (FD.ending (FD.get_fkind a) u2) (FD.starting (FD.get_fkind b) l1) - | Ge, Some true - | Lt, Some false -> meet_bin (FD.starting (FD.get_fkind a) l2) (FD.ending (FD.get_fkind b) u1) - | Lt, Some true - | Ge, Some false -> meet_bin (FD.ending_before (FD.get_fkind a) u2) (FD.starting_after (FD.get_fkind b) l1) - | Gt, Some true - | Le, Some false -> meet_bin (FD.starting_after (FD.get_fkind a) l2) (FD.ending_before (FD.get_fkind b) u1) - | _, _ -> a, b) - | _ -> a, b) - | op -> - if M.tracing then M.tracel "inv" "Unhandled operator %a\n" d_binop op; - a, b + let meet_bin a' b' = FD.meet a a', FD.meet b b' in + try + match op with + | PlusA -> + (* A + B = C, \forall a \in A. a + b_min > pred c_min \land a + b_max < succ c_max + \land a + b_max > pred c_min \land a + b_min < succ c_max + \rightarrow A = [min(pred c_min - b_min, pred c_min - b_max), max(succ c_max - b_max, succ c_max - b_min)] + \rightarrow A = [pred c_min - b_max, succ c_max - b_min] + *) + let reverse_add v v' = (match FD.minimal c, FD.maximal c, FD.minimal v, FD.maximal v with + | Some c_min, Some c_max, Some v_min, Some v_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let l = Float.pred c_min -. v_max in + let h = Float.succ c_max -. v_min in + FD.of_interval (FD.get_fkind c) (l, h) + | _ -> v') in + meet_bin (reverse_add b a) (reverse_add a b) + | MinusA -> + (* A - B = C \ forall a \in A. a - b_max > pred c_min \land a - b_min < succ c_max + \land a - b_min > pred c_min \land a - b_max < succ c_max + \rightarrow A = [min(pred c_min + b_max, pred c_min + b_min), max(succ c_max + b_max, succ c_max + b_max)] + \rightarrow A = [pred c_min + b_min, succ c_max + b_max] + *) + let a' = (match FD.minimal c, FD.maximal c, FD.minimal b, FD.maximal b with + | Some c_min, Some c_max, Some b_min, Some b_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let l = Float.pred c_min +. b_min in + let h = Float.succ c_max +. b_max in + FD.of_interval (FD.get_fkind c) (l, h) + | _ -> a) in + (* A - B = C \ forall b \in B. a_min - b > pred c_min \land a_max - b < succ c_max + \land a_max - b > pred c_min \land a_min - b < succ c_max + \rightarrow B = [min(a_max - succ c_max, a_min - succ c_max), max(a_min - pred c_min, a_max - pred c_min)] + \rightarrow B = [a_min - succ c_max, a_max - pred c_min] + *) + let b' = (match FD.minimal c, FD.maximal c, FD.minimal a, FD.maximal a with + | Some c_min, Some c_max, Some a_min, Some a_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let l = a_min -. Float.succ c_max in + let h = a_max -. Float.pred c_min in + FD.of_interval (FD.get_fkind c) (l, h) + | _ -> b) in + meet_bin a' b' + | Mult -> + (* A * B = C \forall a \in A, a > 0. a * b_min > pred c_min \land a * b_max < succ c_max + A * B = C \forall a \in A, a < 0. a * b_max > pred c_min \land a * b_min < succ c_max + (with negative b reversed <>) + \rightarrow A = [min(pred c_min / b_min, pred c_min / b_max, succ c_max / b_min, succ c_max /b_max), + max(succ c_max / b_min, succ c_max /b_max, pred c_min / b_min, pred c_min / b_max)] + *) + let reverse_mul v v' = (match FD.minimal c, FD.maximal c, FD.minimal v, FD.maximal v with + | Some c_min, Some c_max, Some v_min, Some v_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let v1, v2, v3, v4 = (Float.pred c_min /. v_min), (Float.pred c_min /. v_max), (Float.succ c_max /. v_min), (Float.succ c_max /. v_max) in + let l = Float.min (Float.min v1 v2) (Float.min v3 v4) in + let h = Float.max (Float.max v1 v2) (Float.max v3 v4) in + FD.of_interval (FD.get_fkind c) (l, h) + | _ -> v') in + meet_bin (reverse_mul b a) (reverse_mul a b) + | Div -> + (* A / B = C \forall a \in A, a > 0, b_min > 1. a / b_max > pred c_min \land a / b_min < succ c_max + A / B = C \forall a \in A, a < 0, b_min > 1. a / b_min > pred c_min \land a / b_max < succ c_max + A / B = C \forall a \in A, a > 0, 0 < b_min, b_max < 1. a / b_max > pred c_min \land a / b_min < succ c_max + A / B = C \forall a \in A, a < 0, 0 < b_min, b_max < 1. a / b_min > pred c_min \land a / b_max < succ c_max + ... same for negative b + \rightarrow A = [min(b_max * pred c_min, b_min * pred c_min, b_min * succ c_max, b_max * succ c_max), + max(b_max * succ c_max, b_min * succ c_max, b_max * pred c_min, b_min * pred c_min)] + *) + let a' = (match FD.minimal c, FD.maximal c, FD.minimal b, FD.maximal b with + | Some c_min, Some c_max, Some b_min, Some b_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let v1, v2, v3, v4 = (Float.pred c_min *. b_max), (Float.pred c_min *. b_min), (Float.succ c_max *. b_max), (Float.succ c_max *. b_min) in + let l = Float.min (Float.min v1 v2) (Float.min v3 v4) in + let h = Float.max (Float.max v1 v2) (Float.max v3 v4) in + FD.of_interval (FD.get_fkind c) (l, h) + | _ -> a) in + (* A / B = C \forall b \in B, b > 0, a_min / b > pred c_min \land a_min / b < succ c_max + \land a_max / b > pred c_min \land a_max / b < succ c_max + A / B = C \forall b \in B, b < 0, a_min / b > pred c_min \land a_min / b < succ c_max + \land a_max / b > pred c_min \land a_max / b < succ c_max + \rightarrow (b != 0) B = [min(a_min / succ c_max, a_max / succ c_max, a_min / pred c_min, a_max / pred c_min), + max(a_min / pred c_min, a_max / pred c_min, a_min / succ c_max, a_max / succ c_max)] + *) + let b' = (match FD.minimal c, FD.maximal c, FD.minimal a, FD.maximal a with + | Some c_min, Some c_max, Some a_min, Some a_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let v1, v2, v3, v4 = (a_min /. Float.pred c_min), (a_max /. Float.pred c_min), (a_min /. Float.succ c_max), (a_max /. Float.succ c_max) in + let l = Float.min (Float.min v1 v2) (Float.min v3 v4) in + let h = Float.max (Float.max v1 v2) (Float.max v3 v4) in + FD.of_interval (FD.get_fkind c) (l, h) + | _ -> b) in + meet_bin a' b' + | Eq | Ne as op -> + let both x = x, x in + (match op, ID.to_bool (FD.to_int IBool c) with + | Eq, Some true + | Ne, Some false -> both (FD.meet a b) (* def. equal: if they compare equal, both values must be from the meet *) + | Eq, Some false + | Ne, Some true -> (* def. unequal *) + (* M.debug ~category:Analyzer "Can't use uneqal information about float value in expression \"%a\"." d_plainexp exp; *) + a, b + | _, _ -> a, b + ) + | Lt | Le | Ge | Gt as op -> + (match FD.minimal a, FD.maximal a, FD.minimal b, FD.maximal b with + | Some l1, Some u1, Some l2, Some u2 -> + (match op, ID.to_bool (FD.to_int IBool c) with + | Le, Some true + | Gt, Some false -> meet_bin (FD.ending (FD.get_fkind a) u2) (FD.starting (FD.get_fkind b) l1) + | Ge, Some true + | Lt, Some false -> meet_bin (FD.starting (FD.get_fkind a) l2) (FD.ending (FD.get_fkind b) u1) + | Lt, Some true + | Ge, Some false -> meet_bin (FD.ending_before (FD.get_fkind a) u2) (FD.starting_after (FD.get_fkind b) l1) + | Gt, Some true + | Le, Some false -> meet_bin (FD.starting_after (FD.get_fkind a) l2) (FD.ending_before (FD.get_fkind b) u1) + | _, _ -> a, b) + | _ -> a, b) + | op -> + if M.tracing then M.tracel "inv" "Unhandled operator %a\n" d_binop op; + a, b + with FloatDomain.ArithmeticOnFloatBot _ -> raise Deadcode in let eval e st = eval_rv a gs st e in let eval_bool e st = match eval e st with `Int i -> ID.to_bool i | _ -> None in diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index afe810fabf..7363d55634 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -88,9 +88,9 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct type t = Top | Bot | Interval of (Float_t.t * Float_t.t) [@@deriving eq, ord, to_yojson, hash] let show = function - | Top -> Float_t.name ^ ": [Top]" - | Bot -> Float_t.name ^ ": [Bot]" - | Interval (low, high) -> Printf.sprintf "%s:[%s,%s]" Float_t.name (Float_t.to_string low) (Float_t.to_string high) + | Top -> "[Top]" + | Bot -> "[Bot]" + | Interval (low, high) -> Printf.sprintf "[%s,%s]" (Float_t.to_string low) (Float_t.to_string high) let pretty () x = text (show x) @@ -138,11 +138,6 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | Top -> true | _ -> false - let neg = function - | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "neg %s" (show Bot))) - | Interval (low, high) -> Interval (Float_t.neg high, Float_t.neg low) - | _ -> Top - let norm v = let normed = match v with | Interval (low, high) -> @@ -181,10 +176,12 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let starting_after s = of_interval' (Float_t.succ @@ Float_t.of_float Down s, Float_t.upper_bound) let minimal = function + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "minimal %s" (show Bot))) | Interval (l, _) -> Float_t.to_float l | _ -> None let maximal = function + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "maximal %s" (show Bot))) | Interval (_, h) -> Float_t.to_float h | _ -> None @@ -234,7 +231,13 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | Top, _ -> v2 | _, _ -> v1 - (** evaluation of the binary operations *) + (** evaluation of the unary and binary operations *) + let eval_unop onTop eval_operation op = + match op with + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "unop %s" (show op))) + | Interval v -> eval_operation v + | Top -> onTop + let eval_binop eval_operation op1 op2 = norm @@ match (op1, op2) with | Bot, _ | _, Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "%s op %s" (show op1) (show op2))) @@ -264,6 +267,11 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) + + let eval_neg = function + | (low, high) -> Interval (Float_t.neg high, Float_t.neg low) + + let eval_add (l1, h1) (l2, h2) = Interval (Float_t.add Down l1 l2, Float_t.add Up h1 h2) @@ -337,33 +345,20 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct else if h1 = l1 && h2 = l2 && l1 = l2 then (0, 0) else (0, 1) - let add = eval_binop eval_add + let neg = eval_unop Top eval_neg + let add = eval_binop eval_add let sub = eval_binop eval_sub - let mul = eval_binop eval_mul - let div = eval_binop eval_div let lt = eval_int_binop eval_lt - let gt = eval_int_binop eval_gt - let le = eval_int_binop eval_le - let ge = eval_int_binop eval_ge - let eq = eval_int_binop eval_eq - let ne = eval_int_binop eval_ne - - let eval_unop onTop eval_operation op = - match op with - | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "unop %s" (show op))) - | Interval v -> eval_operation v - | Top -> onTop - let true_nonZero_IInt = IntDomain.IntDomTuple.of_excl_list IInt [(Big_int_Z.big_int_of_int 0)] let false_zero_IInt = IntDomain.IntDomTuple.of_int IInt (Big_int_Z.big_int_of_int 0) let unknown_IInt = IntDomain.IntDomTuple.top_of IInt diff --git a/tests/regression/57-floats/10-svcomp_floats_cbmc_regression_float11.c b/tests/regression/57-floats/10-svcomp_floats_cbmc_regression_float11.c new file mode 100644 index 0000000000..6c6ed81be3 --- /dev/null +++ b/tests/regression/57-floats/10-svcomp_floats_cbmc_regression_float11.c @@ -0,0 +1,49 @@ +// PARAM: --enable ana.float.interval + +/* the primary purpose of this regression test is not checking the assertions, + but showing that the invariant does not <> anymore*/ +int main() +{ + int success = 1; + int unknown = 1; + + // relations + if(!(1.0<2.5)) {success = 0;} + if(!(1.0<=2.5)) {success = 0;} + if(!(1.01<=1.01)) {unknown = 0;} + if(!(2.5>1.0)) {success = 0;} + if(!(2.5>=1.0)) {success = 0;} + if(!(1.01>=1.01)) {unknown = 0;} + if(!(!(1.0>=2.5))) {success = 0;} + if(!(!(1.0>2.5))) {success = 0;} + if(!(1.0!=2.5)) {success = 0;} + + // same flipped + if(!(-1.0>-2.5)) {success = 0;} + if(!(-1.0>=-2.5)) {success = 0;} + if(!(-1.01>=-1.01)) {unknown = 0;} + if(!(-2.5<-1.0)) {success = 0;} + if(!(-2.5<=-1.0)) {success = 0;} + if(!(-1.01<=-1.01)) {unknown = 0;} + if(!(!(-1.0<=-2.5))) {success = 0;} + if(!(!(-1.0<-2.5))) {success = 0;} + if(!(-1.0!=-2.5)) {success = 0;} + + // involving zero + if(!(-1.0<0.0)) {success = 0;} + if(!(0.0>-1.0)) {success = 0;} + if(!(0.0==-0.0)) {success = 0;} + if(!(0.0>=-0.0)) {success = 0;} + if(!(1>0)) {success = 0;} + if(!(0<1)) {success = 0;} + if(!(1>-0)) {success = 0;} + if(!(-0<1)) {success = 0;} + + if(!(!(0.999f<0.0f))) {success = 0;} + if(!(!(-0.999f>-0.0f))) {success = 0;} + if(!(!(0.999f<=0.0f))) {success = 0;} + if(!(!(-0.999f>=-0.0f))) {success = 0;} + + assert(success); // SUCCESS + assert(unknown); // UNKNOWN +} diff --git a/unittest/cdomains/floatDomainTest.ml b/unittest/cdomains/floatDomainTest.ml index cba30e6ae8..c13a757722 100644 --- a/unittest/cdomains/floatDomainTest.ml +++ b/unittest/cdomains/floatDomainTest.ml @@ -237,6 +237,15 @@ struct check_narrow fi_zero fi_one fi_zero; end + let test_FI_ArithmeticOnFloatBot _ = + begin + assert_raises (FloatDomain.ArithmeticOnFloatBot ("minimal "^(FI.show (FI.bot ())))) (fun() -> (FI.minimal (FI.bot ()))); + assert_raises (FloatDomain.ArithmeticOnFloatBot ("to_int "^(FI.show (FI.bot ())))) (fun() -> (FI.to_int IInt (FI.bot ()))); + assert_raises (FloatDomain.ArithmeticOnFloatBot ((FI.show (FI.bot ()))^" op "^(FI.show fi_zero))) (fun() -> (FI.add (FI.bot ()) fi_zero)); + assert_raises (FloatDomain.ArithmeticOnFloatBot ((FI.show (FI.bot ()))^" op "^(FI.show fi_zero))) (fun() -> (FI.lt (FI.bot ()) fi_zero)); + assert_raises (FloatDomain.ArithmeticOnFloatBot ("unop "^(FI.show (FI.bot ())))) (fun() -> (FI.acos (FI.bot ()))); + end + (**interval tests using QCheck arbitraries *) let test_FI_not_bot = QCheck.Test.make ~name:"test_FI_not_bot" (FI.arbitrary ()) (fun arg -> @@ -306,6 +315,7 @@ struct "test_FI_meet_specific" >:: test_FI_leq_specific; "test_FI_widen_specific" >:: test_FI_widen_specific; "test_FI_narrow_specific" >:: test_FI_narrow_specific; + "test_FI_ArithmeticOnFloatBot" >:: test_FI_ArithmeticOnFloatBot; ] let test_qcheck () = QCheck_ounit.to_ounit2_test_list [ From 0b23f7ce3923b463893d9c631319a5e0cd62dcf1 Mon Sep 17 00:00:00 2001 From: Felix <91671586+FelixKrayer@users.noreply.github.com> Date: Wed, 6 Jul 2022 15:43:57 +0200 Subject: [PATCH 34/54] Implement suggestion for LibraryFunctions (math type) (#30) --- src/analyses/base.ml | 81 ++++++++++++++------------------ src/analyses/libraryDesc.ml | 17 ++++++- src/analyses/libraryFunctions.ml | 39 ++++++++++----- 3 files changed, 76 insertions(+), 61 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 297b0d106c..04da414fad 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -2600,52 +2600,41 @@ struct | None -> ctx.local end (**Floating point classification and trigonometric functions defined in c99*) - | Math { args; }, _ -> - begin match args with - | [x] -> - let eval_x = eval_rv (Analyses.ask_of_ctx ctx) gs st x in - begin match eval_x with - | `Float float_x -> - let result = - begin match f.vname with - | "__builtin_isfinite" -> `Int (ID.cast_to IInt (FD.isfinite float_x)) - | "__builtin_isinf" | "__builtin_isinf_sign" -> `Int (ID.cast_to IInt (FD.isinf float_x)) - | "__builtin_isnan" -> `Int (ID.cast_to IInt (FD.isnan float_x)) - | "__builtin_isnormal" -> `Int (ID.cast_to IInt (FD.isnormal float_x)) - | "__builtin_signbit" -> `Int (ID.cast_to IInt (FD.signbit float_x)) - | "__builtin_acos" | "acos" -> `Float (FD.acos float_x) - | "__builtin_asin" | "asin" -> `Float (FD.asin float_x) - | "__builtin_atan" | "atan" -> `Float (FD.atan float_x) - | "__builtin_cos" | "cos" -> `Float (FD.cos float_x) - | "__builtin_sin" | "sin" -> `Float (FD.sin float_x) - | "__builtin_tan" | "tan" -> `Float (FD.tan float_x) - | _ -> failwith (f.vname^" should be implemented in goblint but isn't") - end - in - begin match lv with - | Some lv_val -> set ~ctx (Analyses.ask_of_ctx ctx) gs st (eval_lv (Analyses.ask_of_ctx ctx) ctx.global st lv_val) (Cilfacade.typeOfLval lv_val) result - | None -> st - end - | _ -> failwith ("non-floating-point argument in call to function "^f.vname) - end - | [y; x] -> - let eval_y = eval_rv (Analyses.ask_of_ctx ctx) gs st y in - let eval_x = eval_rv (Analyses.ask_of_ctx ctx) gs st x in - begin match eval_y, eval_x with - | `Float float_y, `Float float_x -> - let result = - begin match f.vname with - | "__builtin_atan2" | "atan2" -> `Float (FD.atan (FD.div float_y float_x)) - | _ -> failwith (f.vname^" should be implemented in goblint but isn't") - end - in - begin match lv with - | Some lv_val -> set ~ctx (Analyses.ask_of_ctx ctx) gs st (eval_lv (Analyses.ask_of_ctx ctx) ctx.global st lv_val) (Cilfacade.typeOfLval lv_val) result - | None -> st - end - | _ -> failwith ("non-floating-point argument in call to function "^f.vname) - end - | _ -> failwith ("strange "^f.vname^" arguments") + | Math { fun_args; }, _ -> + let apply_unary float_fun x = + let eval_x = eval_rv (Analyses.ask_of_ctx ctx) gs st x in + begin match eval_x with + | `Float float_x -> float_fun float_x + | _ -> failwith ("non-floating-point argument in call to function "^f.vname) + end + in + let apply_binary float_fun x y = + let eval_x = eval_rv (Analyses.ask_of_ctx ctx) gs st x in + let eval_y = eval_rv (Analyses.ask_of_ctx ctx) gs st y in + begin match eval_x, eval_y with + | `Float float_x, `Float float_y -> float_fun float_x float_y + | _ -> failwith ("non-floating-point argument in call to function "^f.vname) + end + in + let result = + begin match fun_args with + | Isfinite x -> `Int (ID.cast_to IInt (apply_unary FD.isfinite x)) + | Isinf x -> `Int (ID.cast_to IInt (apply_unary FD.isinf x)) + | Isnan x -> `Int (ID.cast_to IInt (apply_unary FD.isnan x)) + | Isnormal x -> `Int (ID.cast_to IInt (apply_unary FD.isnormal x)) + | Signbit x -> `Int (ID.cast_to IInt (apply_unary FD.signbit x)) + | Acos x -> `Float (apply_unary FD.acos x) + | Asin x -> `Float (apply_unary FD.asin x) + | Atan x -> `Float (apply_unary FD.atan x) + | Atan2 (y, x) -> `Float (apply_binary (fun y' x' -> FD.atan (FD.div y' x')) y x) + | Cos x -> `Float (apply_unary FD.cos x) + | Sin x -> `Float (apply_unary FD.sin x) + | Tan x -> `Float (apply_unary FD.tan x) + end + in + begin match lv with + | Some lv_val -> set ~ctx (Analyses.ask_of_ctx ctx) gs st (eval_lv (Analyses.ask_of_ctx ctx) ctx.global st lv_val) (Cilfacade.typeOfLval lv_val) result + | None -> st end (* handling thread creations *) | ThreadCreate _, _ -> diff --git a/src/analyses/libraryDesc.ml b/src/analyses/libraryDesc.ml index 5072d47f19..d544d7092e 100644 --- a/src/analyses/libraryDesc.ml +++ b/src/analyses/libraryDesc.ml @@ -11,6 +11,20 @@ struct } end +type math = + | Isfinite of Cil.exp + | Isinf of Cil.exp + | Isnan of Cil.exp + | Isnormal of Cil.exp + | Signbit of Cil.exp + | Acos of Cil.exp + | Asin of Cil.exp + | Atan of Cil.exp + | Atan2 of (Cil.exp * Cil.exp) + | Cos of Cil.exp + | Sin of Cil.exp + | Tan of Cil.exp + (** Type of special function, or {!Unknown}. *) (* Use inline record if not single {!Cil.exp} argument. *) type special = @@ -22,7 +36,7 @@ type special = | Unlock of Cil.exp | ThreadCreate of { thread: Cil.exp; start_routine: Cil.exp; arg: Cil.exp; } | ThreadJoin of { thread: Cil.exp; ret_var: Cil.exp; } - | Math of { args: (Cil.exp list); } + | Math of { fun_args: math; } | Memset of { dest: Cil.exp; ch: Cil.exp; count: Cil.exp; } | Bzero of { dest: Cil.exp; count: Cil.exp; } | Abort @@ -89,7 +103,6 @@ let special_of_old classify_name = fun args -> | `Unlock -> Unlock (List.hd args) | `ThreadCreate (thread, start_routine, arg) -> ThreadCreate { thread; start_routine; arg; } | `ThreadJoin (thread, ret_var) -> ThreadJoin { thread; ret_var; } - | `Math args -> Math { args; } | `Unknown _ -> Unknown let of_old ?(attrs: attr list=[]) (old_accesses: Accesses.old) (classify_name): t = { diff --git a/src/analyses/libraryFunctions.ml b/src/analyses/libraryFunctions.ml index d29244e977..006251a9eb 100644 --- a/src/analyses/libraryFunctions.ml +++ b/src/analyses/libraryFunctions.ml @@ -59,6 +59,31 @@ let zstd_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("ZSTD_customFree", unknown [drop "ptr" [f]; drop "customMem" [r]]); ] +(** math functions. + Functions and builtin versions of function and macros defined in math.h. *) +let math_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("__builtin_isfinite", special [__ "x" []] @@ fun x -> Math { fun_args = (Isfinite x) }); + ("__builtin_isinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Isinf x) }); + ("__builtin_isinf_sign", special [__ "x" []] @@ fun x -> Math { fun_args = (Isinf x) }); + ("__builtin_isnan", special [__ "x" []] @@ fun x -> Math { fun_args = (Isnan x) }); + ("__builtin_isnormal", special [__ "x" []] @@ fun x -> Math { fun_args = (Isnormal x) }); + ("__builtin_signbit", special [__ "x" []] @@ fun x -> Math { fun_args = (Signbit x) }); + ("__builtin_acos", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos x) }); + ("acos", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos x) }); + ("__builtin_asin", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin x) }); + ("asin", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin x) }); + ("__builtin_atan", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan x) }); + ("atan", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan x) }); + ("__builtin_atan2", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (y, x)) }); + ("atan2", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (y, x)) }); + ("__builtin_cos", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos x) }); + ("cos", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos x) }); + ("__builtin_sin", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin x) }); + ("sin", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin x) }); + ("__builtin_tan", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan x) }); + ("tan", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan x) }); + ] + (* TODO: allow selecting which lists to use *) let library_descs = Hashtbl.of_list (List.concat [ c_descs_list; @@ -68,6 +93,7 @@ let library_descs = Hashtbl.of_list (List.concat [ linux_descs_list; goblint_descs_list; zstd_descs_list; + math_descs_list; ]) @@ -80,7 +106,6 @@ type categories = [ | `Unlock | `ThreadCreate of exp * exp * exp (* id * f * x *) | `ThreadJoin of exp * exp (* id * ret_var *) - | `Math of exp list | `Unknown of string ] @@ -134,18 +159,6 @@ let classify fn exps = | "pthread_mutex_unlock" | "__pthread_mutex_unlock" | "spin_unlock_irqrestore" | "up_read" | "up_write" | "up" -> `Unlock - | "__builtin_isfinite" | "__builtin_isinf" | "__builtin_isinf_sign" | "__builtin_isnan" | "__builtin_isnormal" - | "__builtin_signbit" | "__builtin_acos" | "acos" | "__builtin_asin" | "asin" | "__builtin_atan" | "atan" - | "__builtin_cos" | "cos" | "__builtin_sin" | "sin" | "__builtin_tan" | "tan" -> - begin match exps with - | [x] -> `Math exps - | _ -> strange_arguments () - end - | "__builtin_atan2" | "atan2" -> - begin match exps with - | [y; x] -> `Math exps - | _ -> strange_arguments () - end | x -> `Unknown x From 4085534b1456bae1b2fc902b18ba8338a5ec8cb5 Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Fri, 8 Jul 2022 07:59:42 +0200 Subject: [PATCH 35/54] Ensure that usage of complex numbers results in top (#31) --- src/analyses/base.ml | 4 ++-- src/cdomains/valueDomain.ml | 11 ++++++----- tests/regression/57-floats/01-base.c | 7 ++++++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 04da414fad..da14753412 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -745,8 +745,8 @@ struct | Const (CInt (num,ikind,str)) -> (match str with Some x -> M.tracel "casto" "CInt (%s, %a, %s)\n" (Cilint.string_of_cilint num) d_ikind ikind x | None -> ()); `Int (ID.cast_to ikind (IntDomain.of_const (num,ikind,str))) - | Const (CReal (_, fkind, Some str)) -> `Float (FD.of_string fkind str) (* prefer parsing from string due to higher precision *) - | Const (CReal (num, fkind, None)) -> `Float (FD.of_const fkind num) + | Const (CReal (_, (FFloat | FDouble | FLongDouble as fkind), Some str)) -> `Float (FD.of_string fkind str) (* prefer parsing from string due to higher precision *) + | Const (CReal (num, (FFloat | FDouble | FLongDouble as fkind), None)) -> `Float (FD.of_const fkind num) (* String literals *) | Const (CStr (x,_)) -> `Address (AD.from_string x) (* normal 8-bit strings, type: char* *) | Const (CWStr (xs,_) as c) -> (* wide character strings, type: wchar_t* *) diff --git a/src/cdomains/valueDomain.ml b/src/cdomains/valueDomain.ml index 077f8fa335..5fa89cc13f 100644 --- a/src/cdomains/valueDomain.ml +++ b/src/cdomains/valueDomain.ml @@ -139,7 +139,7 @@ struct match t with | t when is_mutex_type t -> `Top | TInt (ik,_) -> `Int (ID.top_of ik) - | TFloat (fkind, _) -> `Float (FD.top_of fkind) + | TFloat ((FFloat | FDouble | FLongDouble as fkind), _) -> `Float (FD.top_of fkind) | TPtr _ -> `Address AD.top_ptr | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> init_value fd.ftype) ci) | TComp ({cstruct=false; _},_) -> `Union (Unions.top ()) @@ -155,7 +155,7 @@ struct let rec top_value (t: typ): t = match t with | TInt (ik,_) -> `Int (ID.(cast_to ik (top_of ik))) - | TFloat (fkind, _) -> `Float (FD.top_of fkind) + | TFloat ((FFloat | FDouble | FLongDouble as fkind), _) -> `Float (FD.top_of fkind) | TPtr _ -> `Address AD.top_ptr | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> top_value fd.ftype) ci) | TComp ({cstruct=false; _},_) -> `Union (Unions.top ()) @@ -183,7 +183,7 @@ struct let rec zero_init_value (t:typ): t = match t with | TInt (ikind, _) -> `Int (ID.of_int ikind BI.zero) - | TFloat (fkind, _) -> `Float (FD.of_const fkind 0.0) + | TFloat ((FFloat | FDouble | FLongDouble as fkind), _) -> `Float (FD.of_const fkind 0.0) | TPtr _ -> `Address AD.null_ptr | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> zero_init_value fd.ftype) ci) | TComp ({cstruct=false; _} as ci,_) -> @@ -375,11 +375,12 @@ struct (match Structs.get x first with `Int x -> x | _ -> raise CastError)*) | _ -> log_top __POS__; ID.top_of ik )) - | TFloat (fkind,_) -> + | TFloat ((FFloat | FDouble | FLongDouble as fkind),_) -> (match v with |`Int ix -> `Float (FD.of_int fkind ix) |`Float fx -> `Float (FD.cast_to fkind fx) | _ -> log_top __POS__; `Top) + | TFloat _ -> log_top __POS__; `Top (*ignore complex numbers by going to top*) | TEnum ({ekind=ik; _},_) -> `Int (ID.cast_to ?torg ik (match v with | `Int x -> (* TODO warn if x is not in the constant values of ei.eitems? (which is totally valid (only ik is relevant for wrapping), but might be unintended) *) x @@ -852,7 +853,7 @@ struct (* only return an actual value if we have a type and return actually the exact same type *) | `Float f_value, TFloat(fkind, _) when FD.get_fkind f_value = fkind -> `Float f_value | `Float _, t -> top_value t - | _, TFloat(fkind, _) -> `Float (FD.top_of fkind) + | _, TFloat((FFloat | FDouble | FLongDouble as fkind), _) -> `Float (FD.top_of fkind) | _ -> let x = cast ~torg:l_fld.ftype fld.ftype value in let l', o' = shift_one_over l o in diff --git a/tests/regression/57-floats/01-base.c b/tests/regression/57-floats/01-base.c index 3f4b16ff27..6c37cef483 100644 --- a/tests/regression/57-floats/01-base.c +++ b/tests/regression/57-floats/01-base.c @@ -3,9 +3,14 @@ #include #include #include +#include int main() { + // ensure that complex floats just do not do anything + double _Complex c = 0.; + assert(c == 0.); // UNKNOWN + double x, a = 2., b = 3. + 1; float y, c = 2.f, d = 3.f + 1; long double z, e = 2.l, f = 3.l + 1; @@ -26,7 +31,7 @@ int main() assert(e < 10.f); // SUCCESS assert(e > 10.f); // FAIL - x = (a + b) / 2.; // naive way of computing the middle + x = (a + b) / 2.; // naive way of computing the middle y = (c + d) / 2.; // naive way of computing the middle z = (e + f) / 2.; // naive way of computing the middle From 9b01b750af79a9c48fa5efc8f7e2aae0d0711451 Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Fri, 8 Jul 2022 19:51:35 +0200 Subject: [PATCH 36/54] Different cleanups (#32) - add advanced invariant example - add comments/references to the used papers - remove trailing whitespace --- src/analyses/base.ml | 65 +++++----- src/cdomains/floatDomain.ml | 121 +++++++++--------- src/cdomains/floatOps/floatOps.ml | 2 +- .../57-floats/11-advanced_invariants.c | 57 +++++++++ 4 files changed, 156 insertions(+), 89 deletions(-) create mode 100644 tests/regression/57-floats/11-advanced_invariants.c diff --git a/src/analyses/base.ml b/src/analyses/base.ml index da14753412..14d5d13583 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -175,7 +175,7 @@ struct | Div -> FD.div | _ -> (fun _ _ -> FD.top_of result_fk) - let int_returning_binop_FD = function + let int_returning_binop_FD = function | Lt -> FD.lt | Gt -> FD.gt | Le -> FD.le @@ -184,7 +184,7 @@ struct | Ne -> FD.ne | _ -> (fun _ _ -> ID.top ()) - let is_int_returning_binop_FD = function + let is_int_returning_binop_FD = function | Lt | Gt | Le | Ge | Eq | Ne -> true | _ -> false @@ -258,7 +258,7 @@ struct | `Int v1, `Int v2 -> let result_ik = Cilfacade.get_ikind t in `Int (ID.cast_to result_ik (binop_ID result_ik op v1 v2)) - | `Float v1, `Float v2 when is_int_returning_binop_FD op -> + | `Float v1, `Float v2 when is_int_returning_binop_FD op -> let result_ik = Cilfacade.get_ikind t in `Int (ID.cast_to result_ik (int_returning_binop_FD op v1 v2)) | `Float v1, `Float v2 -> `Float (binop_FD (Cilfacade.get_fkind t) op v1 v2) @@ -1831,6 +1831,11 @@ struct let inv_bin_float (a, b) c op = let open Stdlib in let meet_bin a' b' = FD.meet a a', FD.meet b b' in + (* Refining the abstract values based on branching is rougly based on the idea in [Symbolic execution of floating-point computations](https://hal.inria.fr/inria-00540299/document) + However, their approach is only applicable to the "nearest" rounding mode. Here we use a more general approach covering all possible rounding modes and therefore + use the actual `pred c_min`/`succ c_max` for the outer-bounds instead of the middles between `c_min` and `pred c_min`/`c_max` and `succ c_max` as suggested in the paper. + This also removes the necessarity of computing those expressions with higher precise than in the concrete. + *) try match op with | PlusA -> @@ -1839,34 +1844,34 @@ struct \rightarrow A = [min(pred c_min - b_min, pred c_min - b_max), max(succ c_max - b_max, succ c_max - b_min)] \rightarrow A = [pred c_min - b_max, succ c_max - b_min] *) - let reverse_add v v' = (match FD.minimal c, FD.maximal c, FD.minimal v, FD.maximal v with - | Some c_min, Some c_max, Some v_min, Some v_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let reverse_add v v' = (match FD.minimal c, FD.maximal c, FD.minimal v, FD.maximal v with + | Some c_min, Some c_max, Some v_min, Some v_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> let l = Float.pred c_min -. v_max in let h = Float.succ c_max -. v_min in FD.of_interval (FD.get_fkind c) (l, h) | _ -> v') in meet_bin (reverse_add b a) (reverse_add a b) | MinusA -> - (* A - B = C \ forall a \in A. a - b_max > pred c_min \land a - b_min < succ c_max + (* A - B = C \ forall a \in A. a - b_max > pred c_min \land a - b_min < succ c_max \land a - b_min > pred c_min \land a - b_max < succ c_max \rightarrow A = [min(pred c_min + b_max, pred c_min + b_min), max(succ c_max + b_max, succ c_max + b_max)] \rightarrow A = [pred c_min + b_min, succ c_max + b_max] *) - let a' = (match FD.minimal c, FD.maximal c, FD.minimal b, FD.maximal b with - | Some c_min, Some c_max, Some b_min, Some b_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let a' = (match FD.minimal c, FD.maximal c, FD.minimal b, FD.maximal b with + | Some c_min, Some c_max, Some b_min, Some b_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> let l = Float.pred c_min +. b_min in let h = Float.succ c_max +. b_max in FD.of_interval (FD.get_fkind c) (l, h) | _ -> a) in - (* A - B = C \ forall b \in B. a_min - b > pred c_min \land a_max - b < succ c_max - \land a_max - b > pred c_min \land a_min - b < succ c_max + (* A - B = C \ forall b \in B. a_min - b > pred c_min \land a_max - b < succ c_max + \land a_max - b > pred c_min \land a_min - b < succ c_max \rightarrow B = [min(a_max - succ c_max, a_min - succ c_max), max(a_min - pred c_min, a_max - pred c_min)] \rightarrow B = [a_min - succ c_max, a_max - pred c_min] *) - let b' = (match FD.minimal c, FD.maximal c, FD.minimal a, FD.maximal a with - | Some c_min, Some c_max, Some a_min, Some a_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> - let l = a_min -. Float.succ c_max in - let h = a_max -. Float.pred c_min in + let b' = (match FD.minimal c, FD.maximal c, FD.minimal a, FD.maximal a with + | Some c_min, Some c_max, Some a_min, Some a_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let l = a_min -. Float.succ c_max in + let h = a_max -. Float.pred c_min in FD.of_interval (FD.get_fkind c) (l, h) | _ -> b) in meet_bin a' b' @@ -1874,10 +1879,10 @@ struct (* A * B = C \forall a \in A, a > 0. a * b_min > pred c_min \land a * b_max < succ c_max A * B = C \forall a \in A, a < 0. a * b_max > pred c_min \land a * b_min < succ c_max (with negative b reversed <>) - \rightarrow A = [min(pred c_min / b_min, pred c_min / b_max, succ c_max / b_min, succ c_max /b_max), + \rightarrow A = [min(pred c_min / b_min, pred c_min / b_max, succ c_max / b_min, succ c_max /b_max), max(succ c_max / b_min, succ c_max /b_max, pred c_min / b_min, pred c_min / b_max)] *) - let reverse_mul v v' = (match FD.minimal c, FD.maximal c, FD.minimal v, FD.maximal v with + let reverse_mul v v' = (match FD.minimal c, FD.maximal c, FD.minimal v, FD.maximal v with | Some c_min, Some c_max, Some v_min, Some v_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> let v1, v2, v3, v4 = (Float.pred c_min /. v_min), (Float.pred c_min /. v_max), (Float.succ c_max /. v_min), (Float.succ c_max /. v_max) in let l = Float.min (Float.min v1 v2) (Float.min v3 v4) in @@ -1891,25 +1896,25 @@ struct A / B = C \forall a \in A, a > 0, 0 < b_min, b_max < 1. a / b_max > pred c_min \land a / b_min < succ c_max A / B = C \forall a \in A, a < 0, 0 < b_min, b_max < 1. a / b_min > pred c_min \land a / b_max < succ c_max ... same for negative b - \rightarrow A = [min(b_max * pred c_min, b_min * pred c_min, b_min * succ c_max, b_max * succ c_max), + \rightarrow A = [min(b_max * pred c_min, b_min * pred c_min, b_min * succ c_max, b_max * succ c_max), max(b_max * succ c_max, b_min * succ c_max, b_max * pred c_min, b_min * pred c_min)] *) - let a' = (match FD.minimal c, FD.maximal c, FD.minimal b, FD.maximal b with + let a' = (match FD.minimal c, FD.maximal c, FD.minimal b, FD.maximal b with | Some c_min, Some c_max, Some b_min, Some b_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> let v1, v2, v3, v4 = (Float.pred c_min *. b_max), (Float.pred c_min *. b_min), (Float.succ c_max *. b_max), (Float.succ c_max *. b_min) in - let l = Float.min (Float.min v1 v2) (Float.min v3 v4) in - let h = Float.max (Float.max v1 v2) (Float.max v3 v4) in + let l = Float.min (Float.min v1 v2) (Float.min v3 v4) in + let h = Float.max (Float.max v1 v2) (Float.max v3 v4) in FD.of_interval (FD.get_fkind c) (l, h) | _ -> a) in - (* A / B = C \forall b \in B, b > 0, a_min / b > pred c_min \land a_min / b < succ c_max + (* A / B = C \forall b \in B, b > 0, a_min / b > pred c_min \land a_min / b < succ c_max \land a_max / b > pred c_min \land a_max / b < succ c_max - A / B = C \forall b \in B, b < 0, a_min / b > pred c_min \land a_min / b < succ c_max + A / B = C \forall b \in B, b < 0, a_min / b > pred c_min \land a_min / b < succ c_max \land a_max / b > pred c_min \land a_max / b < succ c_max \rightarrow (b != 0) B = [min(a_min / succ c_max, a_max / succ c_max, a_min / pred c_min, a_max / pred c_min), max(a_min / pred c_min, a_max / pred c_min, a_min / succ c_max, a_max / succ c_max)] *) - let b' = (match FD.minimal c, FD.maximal c, FD.minimal a, FD.maximal a with - | Some c_min, Some c_max, Some a_min, Some a_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> + let b' = (match FD.minimal c, FD.maximal c, FD.minimal a, FD.maximal a with + | Some c_min, Some c_max, Some a_min, Some a_max when Float.is_finite (Float.pred c_min) && Float.is_finite (Float.succ c_max) -> let v1, v2, v3, v4 = (a_min /. Float.pred c_min), (a_max /. Float.pred c_min), (a_min /. Float.succ c_max), (a_max /. Float.succ c_max) in let l = Float.min (Float.min v1 v2) (Float.min v3 v4) in let h = Float.max (Float.max v1 v2) (Float.max v3 v4) in @@ -1994,8 +1999,8 @@ struct (* | `Address a, `Address b -> ... *) | a1, a2 -> fallback ("binop: got abstract values that are not `Int: " ^ sprint VD.pretty a1 ^ " and " ^ sprint VD.pretty a2) st) (* use closures to avoid unused casts *) - in (match c_typed with - | `Int c -> invert_binary_op c ID.pretty (fun ik -> ID.cast_to ik c) (fun fk -> FD.of_int fk c) + in (match c_typed with + | `Int c -> invert_binary_op c ID.pretty (fun ik -> ID.cast_to ik c) (fun fk -> FD.of_int fk c) | `Float c -> invert_binary_op c FD.pretty (fun ik -> FD.to_int ik c) (fun fk -> FD.cast_to fk c) | _ -> failwith "unreachable") | Lval x, `Int _(* meet x with c *) @@ -2022,7 +2027,7 @@ struct set' x v st )) in let t = Cil.unrollType (Cilfacade.typeOfLval x) in (* unroll type to deal with TNamed *) - (match c_typed with + (match c_typed with | `Int c -> update_lval c x (match t with | TPtr _ -> `Address (AD.of_int (module ID) c) | TInt (ik, _) @@ -2601,14 +2606,14 @@ struct end (**Floating point classification and trigonometric functions defined in c99*) | Math { fun_args; }, _ -> - let apply_unary float_fun x = + let apply_unary float_fun x = let eval_x = eval_rv (Analyses.ask_of_ctx ctx) gs st x in begin match eval_x with | `Float float_x -> float_fun float_x | _ -> failwith ("non-floating-point argument in call to function "^f.vname) end in - let apply_binary float_fun x y = + let apply_binary float_fun x y = let eval_x = eval_rv (Analyses.ask_of_ctx ctx) gs st x in let eval_y = eval_rv (Analyses.ask_of_ctx ctx) gs st y in begin match eval_x, eval_y with @@ -2616,7 +2621,7 @@ struct | _ -> failwith ("non-floating-point argument in call to function "^f.vname) end in - let result = + let result = begin match fun_args with | Isfinite x -> `Int (ID.cast_to IInt (apply_unary FD.isfinite x)) | Isinf x -> `Int (ID.cast_to IInt (apply_unary FD.isinf x)) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 7363d55634..415854d031 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -110,12 +110,12 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | Interval (l, h) when ik = IBool && (l > Float_t.zero || h < Float_t.zero) -> IntDomain.IntDomTuple.of_bool IBool true | Interval (l, h) when ik = IBool && l = h && l = Float_t.zero -> IntDomain.IntDomTuple.of_bool IBool false | Interval (l, h) when ik = IBool -> IntDomain.IntDomTuple.top_of IBool - | Interval (l, h) -> + | Interval (l, h) -> (* as converting from float to integer is (exactly) defined as leaving out the fractional part, (value is truncated towrad zero) we do not require specific rounding here *) IntDomain.IntDomTuple.of_interval ik (Float_t.to_big_int l, Float_t.to_big_int h) - let of_int x = + let of_int x = match IntDomain.IntDomTuple.minimal x, IntDomain.IntDomTuple.maximal x with | Some l, Some h when l >= Float_t.to_big_int Float_t.lower_bound && h <= Float_t.to_big_int Float_t.upper_bound -> let l' = Float_t.of_float Down (Big_int_Z.float_of_big_int l) in @@ -128,37 +128,37 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let bot () = Bot - let is_bot = function + let is_bot = function | Bot -> true - | _ -> false + | _ -> false let top () = Top - let is_top = function + let is_top = function | Top -> true - | _ -> false + | _ -> false - let norm v = + let norm v = let normed = match v with - | Interval (low, high) -> - if Float_t.is_finite low && Float_t.is_finite high then + | Interval (low, high) -> + if Float_t.is_finite low && Float_t.is_finite high then if low > high then failwith "invalid Interval" else v else Top | tb -> tb in if is_top normed then - Messages.warn ~category:Messages.Category.Float ~tags:[CWE 189; CWE 739] + Messages.warn ~category:Messages.Category.Float ~tags:[CWE 189; CWE 739] "Float could be +/-infinity or Nan"; normed (**converts "(Float_t.t * Float_t.t) option" arbitraries to "Top | Bot | Interval of (Float_t.t * Float_t.t)". Does not create Bot*) - let convert_arb v = + let convert_arb v = match v with | Some (f1, f2) -> let f1' = Float_t.of_float Nearest f1 in let f2' = Float_t.of_float Nearest f2 in - if Float_t.is_finite f1' && Float_t.is_finite f2' - then Interval (min f1' f2', max f1' f2') + if Float_t.is_finite f1' && Float_t.is_finite f2' + then Interval (min f1' f2', max f1' f2') else Top | _ -> Top @@ -185,11 +185,11 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | Interval (_, h) -> Float_t.to_float h | _ -> None - let is_exact = function - | Interval (l, v) -> l = v + let is_exact = function + | Interval (l, v) -> l = v | _ -> false - let leq v1 v2 = + let leq v1 v2 = match v1, v2 with | _, Top -> true | Top, _ -> false @@ -197,20 +197,20 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | _, Bot -> false | Interval (l1, h1), Interval (l2, h2) -> l1 >= l2 && h1 <= h2 - let join v1 v2 = + let join v1 v2 = match v1, v2 with | Top, _ | _, Top -> Top | Bot, v | v, Bot -> v | Interval (l1, h1), Interval (l2, h2) -> Interval (min l1 l2, max h1 h2) - let meet v1 v2 = + let meet v1 v2 = match v1, v2 with | Bot, _ | _, Bot -> Bot | Top, v | v, Top -> v - | Interval (l1, h1), Interval (l2, h2) -> + | Interval (l1, h1), Interval (l2, h2) -> let (l, h) = (max l1 l2, min h1 h2) in - if l <= h - then Interval (l, h) + if l <= h + then Interval (l, h) else Bot (** [widen x y] assumes [leq x y]. Solvers guarantee this by calling [widen old (join old new)]. *) @@ -219,9 +219,9 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | Top, _ | _, Top -> Top | Bot, v | v, Bot -> v | Interval (l1, h1), Interval (l2, h2) -> - (**If we widen and we know that neither interval contains +-inf or nan, it is ok to widen only to +-max_float, + (**If we widen and we know that neither interval contains +-inf or nan, it is ok to widen only to +-max_float, because a widening with +-inf/nan will always result in the case above -> Top *) - let low = if l1 <= l2 then l1 else Float_t.lower_bound in + let low = if l1 <= l2 then l1 else Float_t.lower_bound in let high = if h1 >= h2 then h1 else Float_t.upper_bound in norm @@ Interval (low, high) @@ -239,43 +239,43 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | Top -> onTop let eval_binop eval_operation op1 op2 = - norm @@ match (op1, op2) with + norm @@ match (op1, op2) with | Bot, _ | _, Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "%s op %s" (show op1) (show op2))) - | Interval v1, Interval v2 -> + | Interval v1, Interval v2 -> let is_exact (lower, upper) = (lower = upper) in let is_exact_before = is_exact v1 && is_exact v2 in let result = eval_operation v1 v2 in (match result with | Interval (r1, r2) -> let is_exact_after = is_exact (r1, r2) - in if not is_exact_after && is_exact_before then + in if not is_exact_after && is_exact_before then Messages.warn - ~category:Messages.Category.Float + ~category:Messages.Category.Float ~tags:[CWE 197; CWE 681; CWE 1339] - "The result of this operation is not exact, even though the inputs were exact."; + "The result of this operation is not exact, even though the inputs were exact."; result | bt -> bt) | _ -> Top let eval_int_binop eval_operation (op1: t) op2 = let a, b = - match (op1, op2) with + match (op1, op2) with | Bot, _ | _, Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "%s op %s" (show op1) (show op2))) - | Interval v1, Interval v2 -> eval_operation v1 v2 + | Interval v1, Interval v2 -> eval_operation v1 v2 | _ -> (0, 1) in IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) - let eval_neg = function + let eval_neg = function | (low, high) -> Interval (Float_t.neg high, Float_t.neg low) - let eval_add (l1, h1) (l2, h2) = + let eval_add (l1, h1) (l2, h2) = Interval (Float_t.add Down l1 l2, Float_t.add Up h1 h2) - let eval_sub (l1, h1) (l2, h2) = + let eval_sub (l1, h1) (l2, h2) = Interval (Float_t.sub Down l1 h2, Float_t.sub Up h1 l2) let eval_mul (l1, h1) (l2, h2) = @@ -307,29 +307,29 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct Interval (low, high) - let eval_lt (l1, h1) (l2, h2) = + let eval_lt (l1, h1) (l2, h2) = if h1 < l2 then (1, 1) - else if l1 >= h2 then (0, 0) + else if l1 >= h2 then (0, 0) else (0, 1) let eval_gt (l1, h1) (l2, h2) = if l1 > h2 then (1, 1) - else if h1 <= l2 then (0, 0) + else if h1 <= l2 then (0, 0) else (0, 1) let eval_le (l1, h1) (l2, h2) = if h1 <= l2 then (1, 1) - else if l1 > h2 then (0, 0) + else if l1 > h2 then (0, 0) else (0, 1) let eval_ge (l1, h1) (l2, h2) = if l1 >= h2 then (1, 1) - else if h1 < l2 then (0, 0) + else if h1 < l2 then (0, 0) else (0, 1) let eval_eq (l1, h1) (l2, h2) = - Messages.warn - ~category:Messages.Category.Float + Messages.warn + ~category:Messages.Category.Float ~tags:[CWE 1077] "Equality between `double` is dangerous!"; if h1 < l2 || h2 < l1 then (0, 0) @@ -337,8 +337,8 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct else (0, 1) let eval_ne (l1, h1) (l2, h2) = - Messages.warn - ~category:Messages.Category.Float + Messages.warn + ~category:Messages.Category.Float ~tags:[CWE 1077] "Equality/Inequality between `double` is dangerous!"; if h1 < l2 || h2 < l1 then (1, 1) @@ -367,13 +367,13 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let eval_isinf _ = false_zero_IInt - let eval_isnormal = function + let eval_isnormal = function | (l, h) -> if l >= Float_t.smallest || h <= (Float_t.neg (Float_t.smallest)) then true_nonZero_IInt else if l > (Float_t.neg (Float_t.smallest)) && h < Float_t.smallest then false_zero_IInt - else + else unknown_IInt (**it seems strange not to return an explicit 1 for negative numbers, but in c99 signbit is defined as: *) @@ -384,18 +384,18 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | _ -> unknown_IInt (**any interval containing zero has to fall in this case, because we do not distinguish between 0. and -0. *) (**This Constant overapproximates pi to use as bounds for the return values of trigonometric functions *) - let overapprox_pi = 3.1416 + let overapprox_pi = 3.1416 let eval_acos = function | (l, h) when l = h && l = Float_t.of_float Nearest 1. -> of_const 0. (*acos(1) = 0*) - | (l, h) -> + | (l, h) -> if l < (Float_t.of_float Down (-.1.)) || h > (Float_t.of_float Up 1.) then Messages.warn ~category:Messages.Category.Float "Domain error will occur: acos argument is outside of [-1., 1.]"; of_interval (0., (overapprox_pi)) (**could be more exact *) let eval_asin = function | (l, h) when l = h && l = Float_t.zero -> of_const 0. (*asin(0) = 0*) - | (l, h) -> + | (l, h) -> if l < (Float_t.of_float Down (-.1.)) || h > (Float_t.of_float Up 1.) then Messages.warn ~category:Messages.Category.Float "Domain error will occur: asin argument is outside of [-1., 1.]"; div (of_interval ((-. overapprox_pi), overapprox_pi)) (of_const 2.) (**could be more exact *) @@ -464,14 +464,19 @@ end module FloatIntervalImplLifted = struct include Printable.Std (* for default invariant, tag and relift *) - type t = - | F32 of F32Interval.t - | F64 of F64Interval.t - | FLong of F64Interval.t [@@deriving to_yojson, eq, ord, hash] module F1 = F32Interval module F2 = F64Interval + (* As described in [Relational Abstract Domains for the Detection of Floating-Point Run-Time Errors](https://www-apr.lip6.fr/~mine/publi/article-mine-esop04.pdf) + the over-approximating interval analysis can do the abstract computations with less precision than the concrete ones. We make use of this in order to also + provide a domain for long doubles although in the abstract we "only" use double precision + *) + type t = + | F32 of F1.t + | F64 of F2.t + | FLong of F2.t [@@deriving to_yojson, eq, ord, hash] + let show = function | F32 a -> "float: " ^ F1.show a | F64 a -> "double: " ^ F2.show a @@ -503,7 +508,7 @@ module FloatIntervalImplLifted = struct | FDouble -> F64 (op64 ()) | FLongDouble -> FLong (op64 ()) | _ -> - (* this sould never be reached, as we have to check for invalid fkind elsewhere, + (* this sould never be reached, as we have to check for invalid fkind elsewhere, however we could instead of crashing also return top_of some fkind to avoid this and nonetheless have no actual information about anything*) failwith "unsupported fkind" @@ -537,10 +542,10 @@ module FloatIntervalImplLifted = struct let top () = failwith "top () is not implemented for FloatIntervalImplLifted." let is_top = dispatch (F1.is_bot, F2.is_bot) - let get_fkind = function + let get_fkind = function | F32 _ -> FFloat | F64 _ -> FDouble - | FLong _ -> FLongDouble + | FLong _ -> FLongDouble let leq = lift2_cmp (F1.leq, F2.leq) let join = lift2 (F1.join, F2.join) @@ -555,7 +560,7 @@ module FloatIntervalImplLifted = struct let printXml o = dispatch (F1.printXml o, F2.printXml o) (* TODO add fkind to output *) (* This is for debugging *) - let name () = "FloatIntervalImplLifted(F32|F64)" + let name () = "FloatIntervalImplLifted" let to_yojson = dispatch (F1.to_yojson, F2.to_yojson) let tag = dispatch (F1.tag, F2.tag) let arbitrary fk = failwith @@ "Arbitrary not implement for " ^ (name ()) ^ "." @@ -572,8 +577,8 @@ module FloatIntervalImplLifted = struct let maximal = dispatch (F1.maximal, F2.maximal) let to_int ikind = dispatch (F1.to_int ikind, F2.to_int ikind) let cast_to fkind = - let create_interval fkind l h = - match l, h with + let create_interval fkind l h = + match l, h with | Some l, Some h -> of_interval fkind (l,h) | Some l, None -> starting fkind l | None, Some h -> ending fkind h @@ -603,7 +608,7 @@ module FloatDomTupleImpl = struct let name () = "floatdomtuple" type 'a m = (module FloatDomain with type t = 'a) - (* only first-order polymorphism on functions + (* only first-order polymorphism on functions -> use records to get around monomorphism restriction on arguments (Same trick as used in intDomain) *) type 'b poly_in = { fi : 'a. 'a m -> 'b -> 'a } type 'b poly_pr = { fp : 'a. 'a m -> 'a -> 'b } diff --git a/src/cdomains/floatOps/floatOps.ml b/src/cdomains/floatOps/floatOps.ml index b331680507..9e57ee9502 100644 --- a/src/cdomains/floatOps/floatOps.ml +++ b/src/cdomains/floatOps/floatOps.ml @@ -88,7 +88,7 @@ module CFloat = struct let to_float x = Some x let to_big_int = big_int_of_float - let is_finite x = Float.is_finite x && x >= lower_bound && x <= upper_bound + let is_finite x = Float.is_finite x && x >= lower_bound && x <= upper_bound let hash = Hashtbl.hash let to_string = Float.to_string diff --git a/tests/regression/57-floats/11-advanced_invariants.c b/tests/regression/57-floats/11-advanced_invariants.c new file mode 100644 index 0000000000..b15c60e02a --- /dev/null +++ b/tests/regression/57-floats/11-advanced_invariants.c @@ -0,0 +1,57 @@ +// PARAM: --enable ana.float.interval --enable ana.int.interval +#include +#include +#include + +int main() +{ + double a; + int d; + + // a pretty high number + double high_number = 340281981556000088756250604298070654976.; + double next = 340281981556000126535182467255232364544.; + double distance_to_next = next - high_number; + assert(high_number + distance_to_next == next); // SUCCESS + + // make a definitly finite! + if (d) + { + a = 2 * high_number; + } + else + { + a = -2 * high_number; + }; + + if (a + high_number <= high_number) + { + // the mathematical solution with `a <= 0` is obviously not the only one here + assert(a <= 0.); // UNKNOWN! + // assuming the rounding mode "nearest", `a` had to be below `distance_to_next / 2` + // (as half-way cases are always rounded away from zero) + assert(a < (distance_to_next / 2)); // UNKNOWN! + // ... this than also has to be unknown + assert(a <= (distance_to_next / 2)); // UNKNOWN! + // with up/down as rounding mode -> a only has to be below `distance_to_next` + assert(a < distance_to_next); // UNKNOWN + assert(a <= distance_to_next); // SUCCESS + } + + double one = 1.; + if (a * one >= next) + { + // the mathematical solution again would be `a >= next` but does not take the rounding mode into account + assert(a >= next); // UNKNOWN! + // assuming the rounding mode "nearest", `a` had to be at least `high_number + distance_to_next / 2` + // (as half-way cases are always rounded away from zero) + assert(a - high_number >= (distance_to_next / 2)); // UNKNOWN! + // ... this than also has to be unknown + assert(a - high_number > (distance_to_next / 2)); // UNKNOWN! + // with up/down as rounding mode -> a only has to be above `high_number` + assert(a > high_number); // UNKNOWN + assert(a >= high_number); // SUCCESS + } + + return 0; +} From 5eb5c401082e119015e03ce84008c2959f11f53c Mon Sep 17 00:00:00 2001 From: Felix <91671586+FelixKrayer@users.noreply.github.com> Date: Fri, 8 Jul 2022 22:01:42 +0200 Subject: [PATCH 37/54] fix wording of Warning for Domain Error and add -f, -l trig. functions (#33) * fix wording of warning for trigonometric domain errors * add -f and -l trigonometric functions --- src/analyses/libraryFunctions.ml | 14 ++++++++++++++ src/cdomains/floatDomain.ml | 4 ++-- tests/regression/57-floats/06-library_functions.c | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/analyses/libraryFunctions.ml b/src/analyses/libraryFunctions.ml index 006251a9eb..274248eaa8 100644 --- a/src/analyses/libraryFunctions.ml +++ b/src/analyses/libraryFunctions.ml @@ -70,18 +70,32 @@ let math_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("__builtin_signbit", special [__ "x" []] @@ fun x -> Math { fun_args = (Signbit x) }); ("__builtin_acos", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos x) }); ("acos", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos x) }); + ("acosf", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos x) }); + ("acosl", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos x) }); ("__builtin_asin", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin x) }); ("asin", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin x) }); + ("asinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin x) }); + ("asinl", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin x) }); ("__builtin_atan", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan x) }); ("atan", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan x) }); + ("atanf", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan x) }); + ("atanl", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan x) }); ("__builtin_atan2", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (y, x)) }); ("atan2", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (y, x)) }); + ("atan2l", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (y, x)) }); + ("atan2f", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (y, x)) }); ("__builtin_cos", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos x) }); ("cos", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos x) }); + ("cosf", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos x) }); + ("cosl", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos x) }); ("__builtin_sin", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin x) }); ("sin", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin x) }); + ("sinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin x) }); + ("sinl", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin x) }); ("__builtin_tan", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan x) }); ("tan", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan x) }); + ("tanf", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan x) }); + ("tanl", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan x) }); ] (* TODO: allow selecting which lists to use *) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 415854d031..057ed66cad 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -390,14 +390,14 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | (l, h) when l = h && l = Float_t.of_float Nearest 1. -> of_const 0. (*acos(1) = 0*) | (l, h) -> if l < (Float_t.of_float Down (-.1.)) || h > (Float_t.of_float Up 1.) then - Messages.warn ~category:Messages.Category.Float "Domain error will occur: acos argument is outside of [-1., 1.]"; + Messages.warn ~category:Messages.Category.Float "Domain error might occur: acos argument might be outside of [-1., 1.]"; of_interval (0., (overapprox_pi)) (**could be more exact *) let eval_asin = function | (l, h) when l = h && l = Float_t.zero -> of_const 0. (*asin(0) = 0*) | (l, h) -> if l < (Float_t.of_float Down (-.1.)) || h > (Float_t.of_float Up 1.) then - Messages.warn ~category:Messages.Category.Float "Domain error will occur: asin argument is outside of [-1., 1.]"; + Messages.warn ~category:Messages.Category.Float "Domain error might occur: asin argument might be outside of [-1., 1.]"; div (of_interval ((-. overapprox_pi), overapprox_pi)) (of_const 2.) (**could be more exact *) let eval_atan = function diff --git a/tests/regression/57-floats/06-library_functions.c b/tests/regression/57-floats/06-library_functions.c index 52c37c6704..3e3d0197f4 100644 --- a/tests/regression/57-floats/06-library_functions.c +++ b/tests/regression/57-floats/06-library_functions.c @@ -49,12 +49,12 @@ int main() // acos(x): assert((0. <= acos(0.1)) && (acos(0.1) <= greater_than_pi)); // SUCCESS assert(acos(1.) == 0.); // SUCCESS - acos(2.0); // WARN: Domain error will occur: acos argument is outside of [-1., 1.] + acos(2.0); // WARN: Domain error might occur: acos argument might be outside of [-1., 1.] // asin(x): assert(((-greater_than_pi / 2.) <= asin(0.1)) && (asin(0.1) <= (greater_than_pi / 2.))); // SUCCESS assert(asin(0.) == 0.); // SUCCESS - asin(2.0); // WARN: Domain error will occur: asin argument is outside of [-1., 1.] + asin(2.0); // WARN: Domain error might occur: asin argument might be outside of [-1., 1.] // atan(x): assert(((-greater_than_pi / 2.) <= atan(0.1)) && (atan(0.1) <= (greater_than_pi / 2.))); // SUCCESS From c44aee4b46299b89794bf9cc66f11b1403053782 Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Mon, 11 Jul 2022 13:16:01 +0200 Subject: [PATCH 38/54] Change warnings (#36) --- src/cdomains/floatDomain.ml | 113 ++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 45 deletions(-) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 057ed66cad..023b1f3e5b 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -92,10 +92,12 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | Bot -> "[Bot]" | Interval (low, high) -> Printf.sprintf "[%s,%s]" (Float_t.to_string low) (Float_t.to_string high) - let pretty () x = text (show x) - - let printXml f (x : t) = - BatPrintf.fprintf f "\n\n%s\n\n\n" (show x) + include Printable.SimpleShow ( + struct + type nonrec t = t + let show = show + end + ) let name () = "FloatInterval" @@ -138,18 +140,13 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | Top -> true | _ -> false - let norm v = - let normed = match v with - | Interval (low, high) -> - if Float_t.is_finite low && Float_t.is_finite high then - if low > high then failwith "invalid Interval" - else v - else Top - | tb -> tb - in if is_top normed then - Messages.warn ~category:Messages.Category.Float ~tags:[CWE 189; CWE 739] - "Float could be +/-infinity or Nan"; - normed + let norm = function + | Interval (low, high) as x -> + if Float_t.is_finite low && Float_t.is_finite high then + if low > high then failwith "invalid Interval" + else x + else Top + | tb -> tb (**converts "(Float_t.t * Float_t.t) option" arbitraries to "Top | Bot | Interval of (Float_t.t * Float_t.t)". Does not create Bot*) let convert_arb v = @@ -165,7 +162,13 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct (**for QCheck: should describe how to generate random values and shrink possible counter examples *) let arbitrary () = QCheck.map convert_arb (QCheck.option (QCheck.pair QCheck.float QCheck.float)) - let of_interval' interval = norm @@ Interval interval + let of_interval' interval = + let x = norm @@ Interval interval + in if is_top x then + Messages.warn ~category:Messages.Category.Float ~tags:[CWE 189; CWE 739] + "Float could be +/-infinity or Nan"; + x + let of_interval (l, h) = of_interval' (Float_t.of_float Down (min l h), Float_t.of_float Up (max l h)) let of_string s = of_interval' (Float_t.atof Down s, Float_t.atof Up s) let of_const f = of_interval (f, f) @@ -233,31 +236,48 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct (** evaluation of the unary and binary operations *) let eval_unop onTop eval_operation op = + if is_top op then + Messages.warn ~category:Messages.Category.Float ~tags:[CWE 189; CWE 739] + "Operand of unary expression could be +/-infinity or Nan"; match op with | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "unop %s" (show op))) | Interval v -> eval_operation v | Top -> onTop let eval_binop eval_operation op1 op2 = - norm @@ match (op1, op2) with + if is_top op1 then + Messages.warn ~category:Messages.Category.Float ~tags:[CWE 189; CWE 739] + "First operand of arithmetic operation could be +/-infinity or Nan"; + if is_top op2 then + Messages.warn ~category:Messages.Category.Float ~tags:[CWE 189; CWE 739] + "Second operand of arithmetic operation could be +/-infinity or Nan"; + match (op1, op2) with | Bot, _ | _, Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "%s op %s" (show op1) (show op2))) | Interval v1, Interval v2 -> - let is_exact (lower, upper) = (lower = upper) in - let is_exact_before = is_exact v1 && is_exact v2 in - let result = eval_operation v1 v2 in + let is_exact_before = is_exact op1 && is_exact op2 in + let result = norm @@ eval_operation v1 v2 in (match result with - | Interval (r1, r2) -> - let is_exact_after = is_exact (r1, r2) - in if not is_exact_after && is_exact_before then - Messages.warn - ~category:Messages.Category.Float - ~tags:[CWE 197; CWE 681; CWE 1339] - "The result of this operation is not exact, even though the inputs were exact."; - result - | bt -> bt) + | Interval (r1, r2) when not (is_exact result) && is_exact_before -> + Messages.warn + ~category:Messages.Category.Float + ~tags:[CWE 197; CWE 681; CWE 1339] + "The result of this operation is not exact, even though the inputs were exact"; + | Top -> + Messages.warn + ~category:Messages.Category.Float + ~tags:[CWE 197; CWE 681; CWE 1339] + "The result of this operation could be +/-infinity or Nan"; + | _ -> ()); + result | _ -> Top let eval_int_binop eval_operation (op1: t) op2 = + if is_top op1 then + Messages.warn ~category:Messages.Category.Float ~tags:[CWE 189; CWE 739] + "First operand of comparison could be +/-infinity or Nan"; + if is_top op2 then + Messages.warn ~category:Messages.Category.Float ~tags:[CWE 189; CWE 739] + "Second operand of comparison could be +/-infinity or Nan"; let a, b = match (op1, op2) with | Bot, _ | _, Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "%s op %s" (show op1) (show op2))) @@ -328,19 +348,11 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct else (0, 1) let eval_eq (l1, h1) (l2, h2) = - Messages.warn - ~category:Messages.Category.Float - ~tags:[CWE 1077] - "Equality between `double` is dangerous!"; if h1 < l2 || h2 < l1 then (0, 0) else if h1 = l1 && h2 = l2 && l1 = l2 then (1, 1) else (0, 1) let eval_ne (l1, h1) (l2, h2) = - Messages.warn - ~category:Messages.Category.Float - ~tags:[CWE 1077] - "Equality/Inequality between `double` is dangerous!"; if h1 < l2 || h2 < l1 then (1, 1) else if h1 = l1 && h2 = l2 && l1 = l2 then (0, 0) else (0, 1) @@ -356,8 +368,18 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let gt = eval_int_binop eval_gt let le = eval_int_binop eval_le let ge = eval_int_binop eval_ge - let eq = eval_int_binop eval_eq - let ne = eval_int_binop eval_ne + let eq a b = + Messages.warn + ~category:Messages.Category.Float + ~tags:[CWE 1077] + "Equality/Inequality between `double` is dangerous!"; + eval_int_binop eval_eq a b + let ne a b = + Messages.warn + ~category:Messages.Category.Float + ~tags:[CWE 1077] + "Equality/Inequality between `double` is dangerous!"; + eval_int_binop eval_ne a b let true_nonZero_IInt = IntDomain.IntDomTuple.of_excl_list IInt [(Big_int_Z.big_int_of_int 0)] let false_zero_IInt = IntDomain.IntDomTuple.of_int IInt (Big_int_Z.big_int_of_int 0) @@ -650,9 +672,6 @@ module FloatDomTupleImpl = struct Option.map_default identity "" (mapp { fp= (fun (type a) (module F : FloatDomain with type t = a) x -> F.name () ^ ":" ^ F.show x); } x) - let to_yojson = - [%to_yojson: Yojson.Safe.t option] - % mapp { fp= (fun (type a) (module F : FloatDomain with type t = a) -> F.to_yojson); } let hash x = Option.map_default identity 0 (mapp { fp= (fun (type a) (module F : FloatDomain with type t = a) -> F.hash); } x) @@ -778,6 +797,10 @@ module FloatDomTupleImpl = struct let pretty_diff () (x, y) = dprintf "%a instead of %a" pretty x pretty y - let printXml f x = - BatPrintf.fprintf f "\n\n%s\n\n\n" (show x) + include Printable.SimpleShow ( + struct + type nonrec t = t + let show = show + end + ) end From 9ef18e9db11ed806d73a21067e2a5885b9f7f4be Mon Sep 17 00:00:00 2001 From: Felix <91671586+FelixKrayer@users.noreply.github.com> Date: Wed, 13 Jul 2022 08:03:55 +0200 Subject: [PATCH 39/54] fix 'Cilfacade.get_ikind: non-integer type double' (#37) Co-authored-by: Felix Krayer --- src/analyses/base.ml | 2 +- tests/regression/57-floats/12-subtraction_assignmen.c | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 tests/regression/57-floats/12-subtraction_assignmen.c diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 7bffd51cb8..545186c923 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -695,7 +695,7 @@ struct in let r = match exp with - | BinOp (op,arg1,arg2,_) -> binop op arg1 arg2 + | BinOp (op,arg1,arg2,_) when Cil.isIntegralType (Cilfacade.typeOf exp) -> binop op arg1 arg2 | _ -> eval_next () in if M.tracing then M.traceu "evalint" "base eval_rv_ask_mustbeequal %a -> %a\n" d_exp exp VD.pretty r; diff --git a/tests/regression/57-floats/12-subtraction_assignmen.c b/tests/regression/57-floats/12-subtraction_assignmen.c new file mode 100644 index 0000000000..d645fbedd8 --- /dev/null +++ b/tests/regression/57-floats/12-subtraction_assignmen.c @@ -0,0 +1,11 @@ +//PARAM: --enable ana.float.interval + +//previously failed in line 7 with "exception Invalid_argument("Cilfacade.get_ikind: non-integer type double ")" +//(same error as in sv-comp: float-newlib/float_req_bl_0220a.c) +int main() { + double z; + + z = 1 - 1.0; + + assert(z == 0.); // SUCCESS +} From 19b76385a06e799b8b361ed9a1ad8f941732046f Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Thu, 14 Jul 2022 10:01:55 +0200 Subject: [PATCH 40/54] Wrap get_ikind in try except to avoid crashing on invalid branching types (#38) --- src/analyses/base.ml | 13 +++++++------ src/util/cilfacade.ml | 4 +--- .../57-floats/12-subtraction_assignmen.c | 15 ++++++++++++--- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 545186c923..f8bbe3b2cd 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -254,10 +254,11 @@ struct in (* The main function! *) match a1,a2 with - (* For the integer values, we apply the domain operator *) + (* For the integer values, we apply the int domain operator *) | `Int v1, `Int v2 -> let result_ik = Cilfacade.get_ikind t in `Int (ID.cast_to result_ik (binop_ID result_ik op v1 v2)) + (* For the float values, we apply the float domain operators *) | `Float v1, `Float v2 when is_int_returning_binop_FD op -> let result_ik = Cilfacade.get_ikind t in `Int (ID.cast_to result_ik (int_returning_binop_FD op v1 v2)) @@ -694,9 +695,9 @@ struct | _ -> eval_next () in let r = - match exp with - | BinOp (op,arg1,arg2,_) when Cil.isIntegralType (Cilfacade.typeOf exp) -> binop op arg1 arg2 - | _ -> eval_next () + match exp with + | BinOp (op,arg1,arg2,_) when Cil.isIntegralType (Cilfacade.typeOf exp) -> binop op arg1 arg2 + | _ -> eval_next () in if M.tracing then M.traceu "evalint" "base eval_rv_ask_mustbeequal %a -> %a\n" d_exp exp VD.pretty r; r @@ -2063,8 +2064,8 @@ struct | _ -> false in let itv = (* int abstraction for tv *) - (* when using floats without explicit cast, we can actually get a non-integer type -> this produces a warning *) - let ik = Cilfacade.get_ikind_exp exp in + (* when using floats without explicit cast, we can actually get a non-integer type *) + let ik = try Cilfacade.get_ikind_exp exp with Invalid_argument _ -> IInt in if not tv || is_cmp exp then (* false is 0, but true can be anything that is not 0, except for comparisons which yield 1 *) ID.of_bool ik tv (* this will give 1 for true which is only ok for comparisons *) else diff --git a/src/util/cilfacade.ml b/src/util/cilfacade.ml index 210dc9dc89..d067e2faa4 100644 --- a/src/util/cilfacade.ml +++ b/src/util/cilfacade.ml @@ -328,9 +328,7 @@ let get_fkind t = (* important to unroll the type here, otherwise problems with typedefs *) match Cil.unrollType t with | TFloat (fk,_) -> fk - | _ -> - Messages.warn "Something that we expected to be a float type has a different type, assuming it is an FDouble"; - Cil.FDouble + | _ -> invalid_arg ("Cilfacade.get_fkind: non-float type " ^ CilType.Typ.show t) let ptrdiff_ikind () = get_ikind !ptrdiffType diff --git a/tests/regression/57-floats/12-subtraction_assignmen.c b/tests/regression/57-floats/12-subtraction_assignmen.c index d645fbedd8..11c21e56c1 100644 --- a/tests/regression/57-floats/12-subtraction_assignmen.c +++ b/tests/regression/57-floats/12-subtraction_assignmen.c @@ -1,11 +1,20 @@ -//PARAM: --enable ana.float.interval +// PARAM: --enable ana.float.interval -//previously failed in line 7 with "exception Invalid_argument("Cilfacade.get_ikind: non-integer type double ")" +// previously failed in line 7 with "exception Invalid_argument("Cilfacade.get_ikind: non-integer type double ")" //(same error as in sv-comp: float-newlib/float_req_bl_0220a.c) -int main() { +// similar error also occured in the additional examples when branching on a float argument +int main() +{ double z; z = 1 - 1.0; assert(z == 0.); // SUCCESS + + if (0.) + ; + + if (0 == (0. + 1.)) + ; + assert(0); // FAIL } From 8c9769cf6ca677908e6ef51310724122e09703bf Mon Sep 17 00:00:00 2001 From: Felix <91671586+FelixKrayer@users.noreply.github.com> Date: Thu, 14 Jul 2022 13:16:26 +0200 Subject: [PATCH 41/54] include missing asserts in regressiontests (#39) --- tests/regression/57-floats/01-base.c | 4 ++-- tests/regression/57-floats/04-casts.c | 4 ++-- tests/regression/57-floats/08-bit_casts.c | 1 + tests/regression/57-floats/09-svcomp_float_req_bl_1252b.c | 1 + .../57-floats/10-svcomp_floats_cbmc_regression_float11.c | 1 + ...12-subtraction_assignmen.c => 12-subtraction_assignment.c} | 1 + 6 files changed, 8 insertions(+), 4 deletions(-) rename tests/regression/57-floats/{12-subtraction_assignmen.c => 12-subtraction_assignment.c} (95%) diff --git a/tests/regression/57-floats/01-base.c b/tests/regression/57-floats/01-base.c index 6c37cef483..86af7068c5 100644 --- a/tests/regression/57-floats/01-base.c +++ b/tests/regression/57-floats/01-base.c @@ -8,8 +8,8 @@ int main() { // ensure that complex floats just do not do anything - double _Complex c = 0.; - assert(c == 0.); // UNKNOWN + double _Complex cplx = 0.; + assert(cplx == 0.); // UNKNOWN double x, a = 2., b = 3. + 1; float y, c = 2.f, d = 3.f + 1; diff --git a/tests/regression/57-floats/04-casts.c b/tests/regression/57-floats/04-casts.c index 30439d36d5..c04c936aa2 100644 --- a/tests/regression/57-floats/04-casts.c +++ b/tests/regression/57-floats/04-casts.c @@ -22,7 +22,7 @@ int main() long l; unsigned u; - // Casts from double/flout/long double into different variants of ints + // Casts from double/float/long double into different variants of ints assert((int)0.0); // FAIL assert((long)0.0); // FAIL assert((unsigned)0.0); // FAIL @@ -43,7 +43,7 @@ int main() assert((long)2.0l); // SUCCESS assert((int)3.0l); // SUCCESS - // Cast from int into double/flaot/long double + // Cast from int into double/float/long double assert((double)0); // FAIL assert((double)0l); // FAIL assert((double)0u); // FAIL diff --git a/tests/regression/57-floats/08-bit_casts.c b/tests/regression/57-floats/08-bit_casts.c index 5d88b96b79..8180a28919 100644 --- a/tests/regression/57-floats/08-bit_casts.c +++ b/tests/regression/57-floats/08-bit_casts.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.float.interval +#include typedef union { diff --git a/tests/regression/57-floats/09-svcomp_float_req_bl_1252b.c b/tests/regression/57-floats/09-svcomp_float_req_bl_1252b.c index 8fae031bea..c4f0302059 100644 --- a/tests/regression/57-floats/09-svcomp_float_req_bl_1252b.c +++ b/tests/regression/57-floats/09-svcomp_float_req_bl_1252b.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.float.interval +#include typedef int __int32_t; typedef unsigned int __uint32_t; diff --git a/tests/regression/57-floats/10-svcomp_floats_cbmc_regression_float11.c b/tests/regression/57-floats/10-svcomp_floats_cbmc_regression_float11.c index 6c6ed81be3..f0951a07c4 100644 --- a/tests/regression/57-floats/10-svcomp_floats_cbmc_regression_float11.c +++ b/tests/regression/57-floats/10-svcomp_floats_cbmc_regression_float11.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.float.interval +#include /* the primary purpose of this regression test is not checking the assertions, but showing that the invariant does not <> anymore*/ diff --git a/tests/regression/57-floats/12-subtraction_assignmen.c b/tests/regression/57-floats/12-subtraction_assignment.c similarity index 95% rename from tests/regression/57-floats/12-subtraction_assignmen.c rename to tests/regression/57-floats/12-subtraction_assignment.c index 11c21e56c1..29adbf81e4 100644 --- a/tests/regression/57-floats/12-subtraction_assignmen.c +++ b/tests/regression/57-floats/12-subtraction_assignment.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.float.interval +#include // previously failed in line 7 with "exception Invalid_argument("Cilfacade.get_ikind: non-integer type double ")" //(same error as in sv-comp: float-newlib/float_req_bl_0220a.c) From d660e4edfeaa36b6ead1dc3096dda9500046a14f Mon Sep 17 00:00:00 2001 From: Felix <91671586+FelixKrayer@users.noreply.github.com> Date: Fri, 15 Jul 2022 10:53:28 +0200 Subject: [PATCH 42/54] add fabs and builtin inf/nan (#40) --- src/analyses/base.ml | 36 +++++----- src/analyses/libraryDesc.ml | 17 +++-- src/analyses/libraryFunctions.ml | 66 +++++++++++-------- src/cdomains/floatDomain.ml | 11 ++++ src/cdomains/floatDomain.mli | 2 + src/cdomains/floatOps/floatOps.ml | 3 + src/cdomains/floatOps/floatOps.mli | 1 + .../57-floats/06-library_functions.c | 10 ++- 8 files changed, 93 insertions(+), 53 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index f8bbe3b2cd..e235502de9 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -2590,35 +2590,39 @@ struct end (**Floating point classification and trigonometric functions defined in c99*) | Math { fun_args; }, _ -> - let apply_unary float_fun x = + let apply_unary fk float_fun x = let eval_x = eval_rv (Analyses.ask_of_ctx ctx) gs st x in begin match eval_x with - | `Float float_x -> float_fun float_x + | `Float float_x -> float_fun (FD.cast_to fk float_x) | _ -> failwith ("non-floating-point argument in call to function "^f.vname) end in - let apply_binary float_fun x y = + let apply_binary fk float_fun x y = let eval_x = eval_rv (Analyses.ask_of_ctx ctx) gs st x in let eval_y = eval_rv (Analyses.ask_of_ctx ctx) gs st y in begin match eval_x, eval_y with - | `Float float_x, `Float float_y -> float_fun float_x float_y + | `Float float_x, `Float float_y -> float_fun (FD.cast_to fk float_x) (FD.cast_to fk float_y) | _ -> failwith ("non-floating-point argument in call to function "^f.vname) end in let result = begin match fun_args with - | Isfinite x -> `Int (ID.cast_to IInt (apply_unary FD.isfinite x)) - | Isinf x -> `Int (ID.cast_to IInt (apply_unary FD.isinf x)) - | Isnan x -> `Int (ID.cast_to IInt (apply_unary FD.isnan x)) - | Isnormal x -> `Int (ID.cast_to IInt (apply_unary FD.isnormal x)) - | Signbit x -> `Int (ID.cast_to IInt (apply_unary FD.signbit x)) - | Acos x -> `Float (apply_unary FD.acos x) - | Asin x -> `Float (apply_unary FD.asin x) - | Atan x -> `Float (apply_unary FD.atan x) - | Atan2 (y, x) -> `Float (apply_binary (fun y' x' -> FD.atan (FD.div y' x')) y x) - | Cos x -> `Float (apply_unary FD.cos x) - | Sin x -> `Float (apply_unary FD.sin x) - | Tan x -> `Float (apply_unary FD.tan x) + | Nan (fk, str) when Cil.isPointerType (Cilfacade.typeOf str) -> `Float (FD.top_of fk) + | Nan _ -> failwith ("non-pointer argument in call to function "^f.vname) + | Inf fk -> `Float (FD.top_of fk) + | Isfinite x -> `Int (ID.cast_to IInt (apply_unary FDouble FD.isfinite x)) + | Isinf x -> `Int (ID.cast_to IInt (apply_unary FDouble FD.isinf x)) + | Isnan x -> `Int (ID.cast_to IInt (apply_unary FDouble FD.isnan x)) + | Isnormal x -> `Int (ID.cast_to IInt (apply_unary FDouble FD.isnormal x)) + | Signbit x -> `Int (ID.cast_to IInt (apply_unary FDouble FD.signbit x)) + | Fabs (fk, x) -> `Float (apply_unary fk FD.fabs x) + | Acos (fk, x) -> `Float (apply_unary fk FD.acos x) + | Asin (fk, x) -> `Float (apply_unary fk FD.asin x) + | Atan (fk, x) -> `Float (apply_unary fk FD.atan x) + | Atan2 (fk, y, x) -> `Float (apply_binary fk (fun y' x' -> FD.atan (FD.div y' x')) y x) + | Cos (fk, x) -> `Float (apply_unary fk FD.cos x) + | Sin (fk, x) -> `Float (apply_unary fk FD.sin x) + | Tan (fk, x) -> `Float (apply_unary fk FD.tan x) end in begin match lv with diff --git a/src/analyses/libraryDesc.ml b/src/analyses/libraryDesc.ml index 6a9ba09cc0..f89947a89f 100644 --- a/src/analyses/libraryDesc.ml +++ b/src/analyses/libraryDesc.ml @@ -12,18 +12,21 @@ struct end type math = + | Nan of (Cil.fkind * Cil.exp) + | Inf of Cil.fkind | Isfinite of Cil.exp | Isinf of Cil.exp | Isnan of Cil.exp | Isnormal of Cil.exp | Signbit of Cil.exp - | Acos of Cil.exp - | Asin of Cil.exp - | Atan of Cil.exp - | Atan2 of (Cil.exp * Cil.exp) - | Cos of Cil.exp - | Sin of Cil.exp - | Tan of Cil.exp + | Fabs of (Cil.fkind * Cil.exp) + | Acos of (Cil.fkind * Cil.exp) + | Asin of (Cil.fkind * Cil.exp) + | Atan of (Cil.fkind * Cil.exp) + | Atan2 of (Cil.fkind * Cil.exp * Cil.exp) + | Cos of (Cil.fkind * Cil.exp) + | Sin of (Cil.fkind * Cil.exp) + | Tan of (Cil.fkind * Cil.exp) (** Type of special function, or {!Unknown}. *) (* Use inline record if not single {!Cil.exp} argument. *) diff --git a/src/analyses/libraryFunctions.ml b/src/analyses/libraryFunctions.ml index cf64cd2f3a..178a55da44 100644 --- a/src/analyses/libraryFunctions.ml +++ b/src/analyses/libraryFunctions.ml @@ -63,40 +63,50 @@ let zstd_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ (** math functions. Functions and builtin versions of function and macros defined in math.h. *) let math_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("__builtin_nan", special [__ "str" []] @@ fun str -> Math { fun_args = (Nan (FDouble, str)) }); + ("__builtin_nanf", special [__ "str" []] @@ fun str -> Math { fun_args = (Nan (FFloat, str)) }); + ("__builtin_nanl", special [__ "str" []] @@ fun str -> Math { fun_args = (Nan (FLongDouble, str)) }); + ("__builtin_inf", special [] @@ Math { fun_args = Inf FDouble}); + ("__builtin_inff", special [] @@ Math { fun_args = Inf FFloat}); + ("__builtin_infl", special [] @@ Math { fun_args = Inf FLongDouble}); ("__builtin_isfinite", special [__ "x" []] @@ fun x -> Math { fun_args = (Isfinite x) }); ("__builtin_isinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Isinf x) }); ("__builtin_isinf_sign", special [__ "x" []] @@ fun x -> Math { fun_args = (Isinf x) }); ("__builtin_isnan", special [__ "x" []] @@ fun x -> Math { fun_args = (Isnan x) }); ("__builtin_isnormal", special [__ "x" []] @@ fun x -> Math { fun_args = (Isnormal x) }); ("__builtin_signbit", special [__ "x" []] @@ fun x -> Math { fun_args = (Signbit x) }); - ("__builtin_acos", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos x) }); - ("acos", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos x) }); - ("acosf", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos x) }); - ("acosl", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos x) }); - ("__builtin_asin", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin x) }); - ("asin", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin x) }); - ("asinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin x) }); - ("asinl", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin x) }); - ("__builtin_atan", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan x) }); - ("atan", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan x) }); - ("atanf", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan x) }); - ("atanl", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan x) }); - ("__builtin_atan2", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (y, x)) }); - ("atan2", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (y, x)) }); - ("atan2l", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (y, x)) }); - ("atan2f", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (y, x)) }); - ("__builtin_cos", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos x) }); - ("cos", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos x) }); - ("cosf", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos x) }); - ("cosl", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos x) }); - ("__builtin_sin", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin x) }); - ("sin", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin x) }); - ("sinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin x) }); - ("sinl", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin x) }); - ("__builtin_tan", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan x) }); - ("tan", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan x) }); - ("tanf", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan x) }); - ("tanl", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan x) }); + ("__builtin_fabs", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FDouble, x)) }); + ("fabs", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FDouble, x)) }); + ("fabsf", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FFloat, x)) }); + ("fabsl", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FLongDouble, x)) }); + ("__builtin_acos", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos (FDouble, x)) }); + ("acos", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos (FDouble, x)) }); + ("acosf", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos (FFloat, x)) }); + ("acosl", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos (FLongDouble, x)) }); + ("__builtin_asin", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin (FDouble, x)) }); + ("asin", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin (FDouble, x)) }); + ("asinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin (FFloat, x)) }); + ("asinl", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin (FLongDouble, x)) }); + ("__builtin_atan", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan (FDouble, x)) }); + ("atan", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan (FDouble, x)) }); + ("atanf", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan (FFloat, x)) }); + ("atanl", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan (FLongDouble, x)) }); + ("__builtin_atan2", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (FDouble, y, x)) }); + ("atan2", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (FDouble, y, x)) }); + ("atan2f", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (FFloat, y, x)) }); + ("atan2l", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (FLongDouble, y, x)) }); + ("__builtin_cos", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos (FDouble, x)) }); + ("cos", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos (FDouble, x)) }); + ("cosf", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos (FFloat, x)) }); + ("cosl", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos (FLongDouble, x)) }); + ("__builtin_sin", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin (FDouble, x)) }); + ("sin", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin (FDouble, x)) }); + ("sinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin (FFloat, x)) }); + ("sinl", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin (FLongDouble, x)) }); + ("__builtin_tan", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan (FDouble, x)) }); + ("tan", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan (FDouble, x)) }); + ("tanf", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan (FFloat, x)) }); + ("tanl", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan (FLongDouble, x)) }); ] (* TODO: allow selecting which lists to use *) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 023b1f3e5b..1e03489800 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -20,6 +20,8 @@ module type FloatArith = sig (** Division: [x / y] *) (** {unary functions} *) + val fabs : t -> t + (** fabs(x) *) val acos : t -> t (** acos(x) *) val asin : t -> t @@ -408,6 +410,11 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct (**This Constant overapproximates pi to use as bounds for the return values of trigonometric functions *) let overapprox_pi = 3.1416 + let eval_fabs = function + | (l, h) when l > Float_t.zero -> Interval (l, h) + | (l, h) when h < Float_t.zero -> neg (Interval (l, h)) + | (l, h) -> Interval (Float_t.zero, max (Float_t.fabs l) (Float_t.fabs h)) + let eval_acos = function | (l, h) when l = h && l = Float_t.of_float Nearest 1. -> of_const 0. (*acos(1) = 0*) | (l, h) -> @@ -444,6 +451,7 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let isnormal = eval_unop unknown_IInt eval_isnormal let signbit = eval_unop unknown_IInt eval_signbit + let fabs = eval_unop (top ()) eval_fabs let acos = eval_unop (top ()) eval_acos let asin = eval_unop (top ()) eval_asin let atan = eval_unop (top ()) eval_atan @@ -535,6 +543,7 @@ module FloatIntervalImplLifted = struct failwith "unsupported fkind" let neg = lift (F1.neg, F2.neg) + let fabs = lift (F1.fabs, F2.fabs) let acos = lift (F1.acos, F2.acos) let asin = lift (F1.asin, F2.asin) let atan = lift (F1.atan, F2.atan) @@ -738,6 +747,8 @@ module FloatDomTupleImpl = struct let neg = map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.neg); } (* f1: unary functions *) + let fabs = + map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.fabs); } let acos = map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.acos); } let asin = diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index ab9da48626..b49f95457c 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -18,6 +18,8 @@ module type FloatArith = sig (** Division: [x / y] *) (** {unary functions} *) + val fabs : t -> t + (** fabs(x) *) val acos : t -> t (** acos(x) *) val asin : t -> t diff --git a/src/cdomains/floatOps/floatOps.ml b/src/cdomains/floatOps/floatOps.ml index 9e57ee9502..5a09d1c69a 100644 --- a/src/cdomains/floatOps/floatOps.ml +++ b/src/cdomains/floatOps/floatOps.ml @@ -28,6 +28,7 @@ module type CFloatType = sig val to_string: t -> string val neg: t -> t + val fabs: t -> t val add: round_mode -> t -> t -> t val sub: round_mode -> t -> t -> t val mul: round_mode -> t -> t -> t @@ -64,6 +65,7 @@ module CDouble = struct let to_string = Float.to_string let neg = Float.neg + let fabs = Float.abs external add: round_mode -> t -> t -> t = "add_double" external sub: round_mode -> t -> t -> t = "sub_double" external mul: round_mode -> t -> t -> t = "mul_double" @@ -94,6 +96,7 @@ module CFloat = struct let to_string = Float.to_string let neg = Float.neg + let fabs = Float.abs external add: round_mode -> t -> t -> t = "add_float" external sub: round_mode -> t -> t -> t = "sub_float" external mul: round_mode -> t -> t -> t = "mul_float" diff --git a/src/cdomains/floatOps/floatOps.mli b/src/cdomains/floatOps/floatOps.mli index bc7982b4a2..6aa55c9e7a 100644 --- a/src/cdomains/floatOps/floatOps.mli +++ b/src/cdomains/floatOps/floatOps.mli @@ -29,6 +29,7 @@ module type CFloatType = sig val neg: t -> t + val fabs: t -> t val add: round_mode -> t -> t -> t val sub: round_mode -> t -> t -> t val mul: round_mode -> t -> t -> t diff --git a/tests/regression/57-floats/06-library_functions.c b/tests/regression/57-floats/06-library_functions.c index 3e3d0197f4..ce8720c95c 100644 --- a/tests/regression/57-floats/06-library_functions.c +++ b/tests/regression/57-floats/06-library_functions.c @@ -6,8 +6,8 @@ int main() { double dbl_min = 2.2250738585072014e-308; - double inf = 1. / 0.; - double nan = 0. / 0.; + double inf = __builtin_inf(); + double nan = __builtin_nan(""); //__buitin_isfinite(x): assert(__builtin_isfinite(1.0)); // SUCCESS @@ -45,6 +45,12 @@ int main() assert(__builtin_signbit(-inf)); // UNKNOWN assert(__builtin_signbit(nan)); // UNKNOWN + // fabs(x): + assert(4. == fabs(-4.)); // SUCCESS + assert(0. <= fabs(cos(0.1))); // SUCCESS + assert(0. <= fabs(-inf)); // UNKNOWN + assert(0. <= fabs(nan)); // UNKNOWN + double greater_than_pi = 3.142; // acos(x): assert((0. <= acos(0.1)) && (acos(0.1) <= greater_than_pi)); // SUCCESS From d31be72f9d80c963fcd6d85de09bb2ce2578d231 Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Tue, 19 Jul 2022 12:47:21 +0200 Subject: [PATCH 43/54] Find another location where get_ikind results in an error (#41) * Find another location where get_ikind results in an error * Further refine determining safe casts with floats --- src/cdomains/valueDomain.ml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/cdomains/valueDomain.ml b/src/cdomains/valueDomain.ml index 5fa89cc13f..4ada05de61 100644 --- a/src/cdomains/valueDomain.ml +++ b/src/cdomains/valueDomain.ml @@ -267,9 +267,14 @@ struct let is_safe_cast t2 t1 = match t2, t1 with (*| TPtr _, t -> bitsSizeOf t <= bitsSizeOf !upointType | t, TPtr _ -> bitsSizeOf t >= bitsSizeOf !upointType*) - | TInt (ik,_), TFloat (fk,_) (* does a1 fit into ik's range? *) - | TFloat (fk,_), TInt (ik,_) (* can a1 be represented as fk? *) - -> false (* TODO precision *) + | TFloat (fk1,_), TFloat (fk2,_) when fk1 = fk2 -> true + | TFloat (FDouble,_), TFloat (FFloat,_) -> true + | TFloat (FLongDouble,_), TFloat (FFloat,_) -> true + | TFloat (FLongDouble,_), TFloat (FDouble,_) -> true + | _, TFloat _ -> false (* casting float to an integral type always looses the decimals *) + | TFloat ((FFloat | FDouble | FLongDouble), _), TInt((IBool | IChar | IUChar | ISChar | IShort | IUShort), _) -> true (* resonably small integers can be stored in all fkinds *) + | TFloat ((FDouble | FLongDouble), _), TInt((IInt | IUInt | ILong | IULong), _) -> true (* values stored in between 16 and 32 bits can only be stored in at least doubles *) + | TFloat _, _ -> false (* all wider integers can not be completly put into a float, partially because our internal representation of long double is the same as for doubles *) | _ -> IntDomain.Size.is_cast_injective ~from_type:t1 ~to_type:t2 && bitsSizeOf t2 >= bitsSizeOf t1 (*| _ -> false*) From 380ec34c2991a87ee0262248fc01cf30602a8b3b Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Wed, 20 Jul 2022 21:29:24 +0200 Subject: [PATCH 44/54] Improve narrowing by introducing additional step (#42) --- src/cdomains/floatDomain.ml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 1e03489800..6569eb8285 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -234,7 +234,11 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct match v1, v2 with (**we cannot distinguish between the lower bound beeing -inf or the upper bound beeing inf. Also there is nan *) | Bot, _ | _, Bot -> Bot | Top, _ -> v2 - | _, _ -> v1 + | Interval (l1, h1), Interval (l2, h2) -> + let low = if l1 = Float_t.lower_bound then l2 else l1 in + let high = if h1 = Float_t.upper_bound then h2 else h1 in + norm @@ Interval (low, high) + | Interval _, Top -> v1 (** evaluation of the unary and binary operations *) let eval_unop onTop eval_operation op = From 437ec6e8bcfc35a3666a1d22cae57867bb19db8a Mon Sep 17 00:00:00 2001 From: Michael Schwarz Date: Tue, 26 Jul 2022 13:35:03 +0200 Subject: [PATCH 45/54] Fix typo Co-authored-by: Julian Erhard --- src/cdomains/floatDomain.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 6569eb8285..178d294525 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -116,7 +116,7 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | Interval (l, h) when ik = IBool -> IntDomain.IntDomTuple.top_of IBool | Interval (l, h) -> (* as converting from float to integer is (exactly) defined as leaving out the fractional part, - (value is truncated towrad zero) we do not require specific rounding here *) + (value is truncated toward zero) we do not require specific rounding here *) IntDomain.IntDomTuple.of_interval ik (Float_t.to_big_int l, Float_t.to_big_int h) let of_int x = From c6a31b64d9f980ef3e26bd26e4688788c80d310b Mon Sep 17 00:00:00 2001 From: Michael Schwarz Date: Tue, 26 Jul 2022 13:41:56 +0200 Subject: [PATCH 46/54] Modify test 57/12 to actually trigger the issue --- tests/regression/57-floats/12-subtraction_assignment.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/regression/57-floats/12-subtraction_assignment.c b/tests/regression/57-floats/12-subtraction_assignment.c index 29adbf81e4..8f3627921f 100644 --- a/tests/regression/57-floats/12-subtraction_assignment.c +++ b/tests/regression/57-floats/12-subtraction_assignment.c @@ -3,19 +3,19 @@ // previously failed in line 7 with "exception Invalid_argument("Cilfacade.get_ikind: non-integer type double ")" //(same error as in sv-comp: float-newlib/float_req_bl_0220a.c) -// similar error also occured in the additional examples when branching on a float argument +// similar error also occurred in the additional examples when branching on a float argument int main() { double z; + int x; z = 1 - 1.0; assert(z == 0.); // SUCCESS - if (0.) - ; + if (0.) { x = z;} + + if (0 == (0. + 1.)) { x = z;} - if (0 == (0. + 1.)) - ; assert(0); // FAIL } From c21875ded9da89402660f3fae3c1cf65ccb491e4 Mon Sep 17 00:00:00 2001 From: Michael Schwarz Date: Tue, 26 Jul 2022 13:54:28 +0200 Subject: [PATCH 47/54] Whitespace --- src/analyses/base.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 304d4a3e23..73ce30d016 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -2022,7 +2022,7 @@ struct | `Int c -> update_lval c x (match t with | TPtr _ -> `Address (AD.of_int (module ID) c) | TInt (ik, _) - | TEnum ({ekind = ik; _}, _) -> `Int (ID.cast_to ik c ) + | TEnum ({ekind = ik; _}, _) -> `Int (ID.cast_to ik c) | TFloat (fk, _) -> `Float (FD.of_int fk c) | _ -> `Int c) ID.pretty | `Float c -> update_lval c x (match t with From 84d5ca5941d589001a60fc8144c15d7dafeb0c82 Mon Sep 17 00:00:00 2001 From: Michael Schwarz Date: Tue, 26 Jul 2022 16:56:22 +0200 Subject: [PATCH 48/54] Add unsoundness example --- tests/regression/57-floats/13-refine-branch.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/regression/57-floats/13-refine-branch.c diff --git a/tests/regression/57-floats/13-refine-branch.c b/tests/regression/57-floats/13-refine-branch.c new file mode 100644 index 0000000000..b50fc7a393 --- /dev/null +++ b/tests/regression/57-floats/13-refine-branch.c @@ -0,0 +1,15 @@ +// PARAM: --enable ana.float.interval +#include +int main() +{ + double z; + int x; + + if(z) { + // z may NOT be refined to range only in the values of int here(!) + assert(__builtin_isfinite(z)); //UNKNOWN! + } else { + + } + +} From 93d6f9130436a0fac681e80f84a4526a9165c4ad Mon Sep 17 00:00:00 2001 From: Michael Schwarz Date: Tue, 26 Jul 2022 17:02:44 +0200 Subject: [PATCH 49/54] Typo --- src/cdomains/floatDomain.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 178d294525..cecc19ef99 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -9,7 +9,7 @@ module type FloatArith = sig type t val neg : t -> t - (** Negating an flaot value: [-x] *) + (** Negating an float value: [-x] *) val add : t -> t -> t (** Addition: [x + y] *) val sub : t -> t -> t From 1600513cf851f72021cc7587c3266242ef888f2d Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Tue, 26 Jul 2022 17:39:51 +0200 Subject: [PATCH 50/54] Fix unsoundness with floats in branches by going to top for those cases --- src/analyses/base.ml | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 73ce30d016..a74a81e75f 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -2065,16 +2065,18 @@ struct let is_cmp = function | BinOp ((Lt | Gt | Le | Ge | Eq | Ne), _, _, t) -> true | _ -> false - in - let itv = (* int abstraction for tv *) - (* when using floats without explicit cast, we can actually get a non-integer type *) - let ik = try Cilfacade.get_ikind_exp exp with Invalid_argument _ -> IInt in - if not tv || is_cmp exp then (* false is 0, but true can be anything that is not 0, except for comparisons which yield 1 *) - ID.of_bool ik tv (* this will give 1 for true which is only ok for comparisons *) - else - ID.of_excl_list ik [BI.zero] (* Lvals, Casts, arithmetic operations etc. should work with true = non_zero *) - in - inv_exp (`Int itv) exp st + in + try + let ik = Cilfacade.get_ikind_exp exp in + let itv = if not tv || is_cmp exp then (* false is 0, but true can be anything that is not 0, except for comparisons which yield 1 *) + ID.of_bool ik tv (* this will give 1 for true which is only ok for comparisons *) + else + ID.of_excl_list ik [BI.zero] (* Lvals, Casts, arithmetic operations etc. should work with true = non_zero *) + in + inv_exp (`Int itv) exp st + with Invalid_argument _ -> + inv_exp (`Float (FD.top_of (Cilfacade.get_fkind_exp exp))) exp st + let set_savetop ~ctx ?lval_raw ?rval_raw ask (gs:glob_fun) st adr lval_t v : store = if M.tracing then M.tracel "set" "savetop %a %a %a\n" AD.pretty adr d_type lval_t VD.pretty v; From 58309129a5c0170d0735456c703ced2b2b5ba9e9 Mon Sep 17 00:00:00 2001 From: Dudeldu Date: Tue, 26 Jul 2022 18:15:41 +0200 Subject: [PATCH 51/54] Refine branching on floats for negative case --- src/analyses/base.ml | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index a74a81e75f..8005ca0f0e 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -2065,17 +2065,23 @@ struct let is_cmp = function | BinOp ((Lt | Gt | Le | Ge | Eq | Ne), _, _, t) -> true | _ -> false - in - try - let ik = Cilfacade.get_ikind_exp exp in - let itv = if not tv || is_cmp exp then (* false is 0, but true can be anything that is not 0, except for comparisons which yield 1 *) + in + try + let ik = Cilfacade.get_ikind_exp exp in + let itv = if not tv || is_cmp exp then (* false is 0, but true can be anything that is not 0, except for comparisons which yield 1 *) ID.of_bool ik tv (* this will give 1 for true which is only ok for comparisons *) else ID.of_excl_list ik [BI.zero] (* Lvals, Casts, arithmetic operations etc. should work with true = non_zero *) - in - inv_exp (`Int itv) exp st - with Invalid_argument _ -> - inv_exp (`Float (FD.top_of (Cilfacade.get_fkind_exp exp))) exp st + in + inv_exp (`Int itv) exp st + with Invalid_argument _ -> + let fk = Cilfacade.get_fkind_exp exp in + let ftv = if not tv then (* false is 0, but true can be anything that is not 0, except for comparisons which yield 1 *) + FD.of_const fk 0. + else + FD.top_of fk + in + inv_exp (`Float ftv) exp st let set_savetop ~ctx ?lval_raw ?rval_raw ask (gs:glob_fun) st adr lval_t v : store = From 9dcb3b56f7b958f9625663565151f55070e05831 Mon Sep 17 00:00:00 2001 From: Michael Schwarz Date: Wed, 27 Jul 2022 10:04:29 +0200 Subject: [PATCH 52/54] Typos & rename first `invariant` in `base.ml` to `invariant_fallback` --- src/analyses/base.ml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 8005ca0f0e..ab9568d4b5 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -1540,7 +1540,7 @@ struct | `Bot -> false (* HACK: bot is here due to typing conflict (we do not cast appropriately) *) | _ -> VD.is_bot_value x - let invariant ctx a (gs:glob_fun) st exp tv = + let invariant_fallback ctx a (gs:glob_fun) st exp tv = (* We use a recursive helper function so that x != 0 is false can be handled * as x == 0 is true etc *) let rec helper (op: binop) (lval: lval) (value: value) (tv: bool) = @@ -1695,7 +1695,7 @@ struct let invariant ctx a gs st exp tv: store = let fallback reason st = if M.tracing then M.tracel "inv" "Can't handle %a.\n%s\n" d_plainexp exp reason; - invariant ctx a gs st exp tv + invariant_fallback ctx a gs st exp tv in (* inverse values for binary operation a `op` b == c *) (* ikind is the type of a for limiting ranges of the operands a, b. The only binops which can have different types for a, b are Shiftlt, Shiftrt (not handled below; don't use ikind to limit b there). *) @@ -1822,10 +1822,10 @@ struct let inv_bin_float (a, b) c op = let open Stdlib in let meet_bin a' b' = FD.meet a a', FD.meet b b' in - (* Refining the abstract values based on branching is rougly based on the idea in [Symbolic execution of floating-point computations](https://hal.inria.fr/inria-00540299/document) + (* Refining the abstract values based on branching is roughly based on the idea in [Symbolic execution of floating-point computations](https://hal.inria.fr/inria-00540299/document) However, their approach is only applicable to the "nearest" rounding mode. Here we use a more general approach covering all possible rounding modes and therefore use the actual `pred c_min`/`succ c_max` for the outer-bounds instead of the middles between `c_min` and `pred c_min`/`c_max` and `succ c_max` as suggested in the paper. - This also removes the necessarity of computing those expressions with higher precise than in the concrete. + This also removes the necessity of computing those expressions with higher precise than in the concrete. *) try match op with @@ -1919,7 +1919,7 @@ struct | Ne, Some false -> both (FD.meet a b) (* def. equal: if they compare equal, both values must be from the meet *) | Eq, Some false | Ne, Some true -> (* def. unequal *) - (* M.debug ~category:Analyzer "Can't use uneqal information about float value in expression \"%a\"." d_plainexp exp; *) + (* M.debug ~category:Analyzer "Can't use unequal information about float value in expression \"%a\"." d_plainexp exp; *) a, b | _, _ -> a, b ) From e9589fb48dc40e972540c7b5920fe56bc157a5be Mon Sep 17 00:00:00 2001 From: Michael Schwarz Date: Wed, 27 Jul 2022 10:06:02 +0200 Subject: [PATCH 53/54] Typos in `floatDomain.ml` --- src/cdomains/floatDomain.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index cecc19ef99..5ecc07b648 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -542,7 +542,7 @@ module FloatIntervalImplLifted = struct | FDouble -> F64 (op64 ()) | FLongDouble -> FLong (op64 ()) | _ -> - (* this sould never be reached, as we have to check for invalid fkind elsewhere, + (* this should never be reached, as we have to check for invalid fkind elsewhere, however we could instead of crashing also return top_of some fkind to avoid this and nonetheless have no actual information about anything*) failwith "unsupported fkind" From 08a624df882b3482ff66759c380f8f5898b5ca46 Mon Sep 17 00:00:00 2001 From: Michael Schwarz Date: Wed, 27 Jul 2022 10:07:56 +0200 Subject: [PATCH 54/54] Remove CWEs from operations where they don't really apply --- src/cdomains/floatDomain.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 5ecc07b648..d3e8d6f1e3 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -266,12 +266,12 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | Interval (r1, r2) when not (is_exact result) && is_exact_before -> Messages.warn ~category:Messages.Category.Float - ~tags:[CWE 197; CWE 681; CWE 1339] + ~tags:[CWE 1339] "The result of this operation is not exact, even though the inputs were exact"; | Top -> Messages.warn ~category:Messages.Category.Float - ~tags:[CWE 197; CWE 681; CWE 1339] + ~tags:[CWE 1339] "The result of this operation could be +/-infinity or Nan"; | _ -> ()); result