diff --git a/src/plugins/value/legacy/eval_terms.ml b/src/plugins/value/legacy/eval_terms.ml index 9a9fb45daa4ee4e2061a544e312932199ccfe844..13ffb2300d361dc1c7c9e14434997e9e78116197 100644 --- a/src/plugins/value/legacy/eval_terms.ml +++ b/src/plugins/value/legacy/eval_terms.ml @@ -122,14 +122,23 @@ let display_evaluation_error ~loc = function let alarm_reduce_mode () = if Value_parameters.ReduceOnLogicAlarms.get () then Ignore else Fail -let find_or_alarm ~alarm_mode state loc = +let find_indeterminate ~alarm_mode state loc = let is_invalid = not Locations.(is_valid Read loc) in track_alarms is_invalid alarm_mode; - let v = Model.find_indeterminate ~conflate_bottom:true state loc in + Model.find_indeterminate ~conflate_bottom:true state loc + +let find_or_alarm ~alarm_mode state loc = + let v = find_indeterminate ~alarm_mode state loc in let is_indeterminate = Cvalue.V_Or_Uninitialized.is_indeterminate v in track_alarms is_indeterminate alarm_mode; V_Or_Uninitialized.get_v v +(* Returns true if [loc] contains a memory location definitely invalid + for a memory access of kind [access]. *) +let contains_invalid_loc access loc = + let valid_loc = Locations.valid_part access loc in + not (Locations.Location.equal loc valid_loc) + (* Evaluation environments. Used to evaluate predicate on \at nodes *) (* Labels: @@ -422,21 +431,31 @@ let empty_logic_deps = (* Type holding the result of an evaluation. Currently, 'a is either - [Cvalue.V.t] for [eval_term], and [Location_Bits.t] for - [eval_tlval_as_loc], and [Ival.t] for [eval_toffset]. [eover] - contains an over-approximation of the evaluation. [eunder] contains an + [Cvalue.V.t] for [eval_term], and [Location_Bits.t] for [eval_tlval_as_loc], + and [Ival.t] for [eval_toffset]. + + [eover] is an over-approximation of the evaluation. [eunder] is an under-approximation, under the hypothesis that the state in which we evaluate is not Bottom. (Otherwise, all under-approximations would be - Bottom themselves). The following two invariants should hold: + Bottom themselves). + The following two invariants should hold: (1) eunder \subset eover. (2) when evaluating something that is not a Tset, either eunder = Bottom, or eunder = eover, and cardinal(eover) <= 1. This is due to the fact that under-approximations are not propagated as an abstract domain, but - only created from Trange or inferred from exact over-approximations. *) + only created from Trange or inferred from exact over-approximations. + + This type can be used for the evaluation of logical sets, in which case + an eval_result [r] represents all *non-empty* sets [S] such that: + [r.eunder ⊆ S ⊆ r.eover]. The value {V.bottom} does *not* represents empty + sets, as for most predicates, P(∅) ≠P(⊥) (if the latter has a meaning). + The boolean [empty] indicates whether an eval_result also represents a + possible empty set. *) type 'a eval_result = { etype: Cil_types.typ; eunder: 'a; eover: 'a; + empty: bool; ldeps: logic_deps; } @@ -477,6 +496,7 @@ let inject_no_deps typ cvalue = { etype = typ; eunder = under_from_over cvalue; eover = cvalue; + empty = false; ldeps = empty_logic_deps } (* Integer result with no memory dependencies: constants, sizeof & alignof, @@ -565,7 +585,7 @@ let eval_logic_charlen wrapper env v ldeps = (* the C strlen function has type size_t, but the logic strlen function has type ℤ (signed) *) let etype = Cil.intType in - { etype; ldeps; eover; eunder } + { etype; ldeps; eover; empty = false; eunder } (* Evaluates the logical predicates strchr/wcschr. *) let eval_logic_charchr builtin env s c ldeps_s ldeps_c = @@ -591,7 +611,7 @@ let eval_logic_charchr builtin env s c ldeps_s ldeps_c = type 𔹠*) let etype = TInt (IBool, []) in let ldeps = join_logic_deps ldeps_s ldeps_c in - { etype; ldeps; eover; eunder } + { etype; ldeps; eover; empty = false; eunder } (* Evaluates the logical predicate is_allocable, according to the following logic: @@ -648,7 +668,7 @@ let cast_to_bool r = let contains_zero = V.contains_zero r.eover and contains_non_zero = V.contains_non_zero r.eover in let eover = V.interp_boolean ~contains_zero ~contains_non_zero in - { eover; eunder = under_from_over eover; + { eover; eunder = under_from_over eover; empty = r.empty; ldeps = r.ldeps; etype = TInt (IBool, []) } (* -------------------------------------------------------------------------- *) @@ -776,14 +796,16 @@ let rec eval_term ~alarm_mode env t = { etype = TPtr (r.etype, []); ldeps = r.ldeps; eunder = loc_bits_to_loc_bytes_under r.eunder; - eover = loc_bits_to_loc_bytes r.eover } + eover = loc_bits_to_loc_bytes r.eover; + empty = r.empty; } | TStartOf tlval -> let r = eval_tlval ~alarm_mode env tlval in { etype = TPtr (Cil.typeOf_array_elem r.etype, []); ldeps = r.ldeps; eunder = loc_bits_to_loc_bytes_under r.eunder; - eover = loc_bits_to_loc_bytes r.eover } + eover = loc_bits_to_loc_bytes r.eover; + empty = r.empty; } (* Special case for the constants \pi, \e, \infinity and \NaN. *) | TLval (TVar {lv_name = "\\pi"}, _) -> ereal Fval.pi @@ -825,7 +847,7 @@ let rec eval_term ~alarm_mode env t = in { etype = typ; ldeps = join_logic_deps deps (lval.ldeps); - eunder; eover } + eunder; eover; empty = lval.empty; } (* TBinOp ((LOr | LAnd), _t1, _t2) -> TODO: a special case would be useful. But this requires reducing the state after having evaluated t1 by @@ -843,9 +865,9 @@ let rec eval_term ~alarm_mode env t = let eover = v in { etype = typ'; ldeps = r.ldeps; - eover; eunder = under_from_over eover } + eover; eunder = under_from_over eover; empty = r.empty; } - | Trange(otlow, othigh) -> + | Trange (opt_low, opt_high) -> (* The overapproximation is the range [min(low.eover)..max(high.eover)]. The underapproximation is the range [max(low.eover)..min(high.eover)]. Perhaps surprisingly, we do not use the under-approximations of @@ -856,59 +878,42 @@ let rec eval_term ~alarm_mode env t = eunder is either Bottom, or equal to eover, both being of cardinal one. In both cases, using eover is more precise. *) let deps = ref empty_logic_deps in - let min v = - try (match Ival.min_int (Cvalue.V.project_ival v) with - | None -> `Approx - | Some(x) -> `Finite(x)) - with Cvalue.V.Not_based_on_null -> `Approx - in - let max v = - try (match Ival.max_int (Cvalue.V.project_ival v) with - | None -> `Approx - | Some(x) -> `Finite(x)) - with Cvalue.V.Not_based_on_null -> `Approx - in - (* Evaluate a bound: - - [sure_bound_under] is returned for the under-approximation when the - bound is explicitly omitted in the ACSL term - - [min_max_*] is the function to retrieve the bound from the - over_approximation, for both the underapproximation and the - overapproximation. *) - let eval_bound sure_bound_under min_max_under min_max_over = function - | None -> sure_bound_under, `Approx - | Some(result) -> + (* Evaluates the minimum and maximum integer value of an optional term. + According to the Ival convention, None stands for -∞ and ∞ for the + minimum and maximum respectively. *) + let min_max = function + | None -> None, None + | Some term -> try - let result = eval_term ~alarm_mode env result in + let result = eval_term ~alarm_mode env term in deps := join_logic_deps !deps result.ldeps; - let under = min_max_under result.eover in - let over = min_max_over result.eover in - under, over - with LogicEvalError e -> + let ival = Cvalue.V.project_ival result.eover in + Ival.min_int ival, Ival.max_int ival + with + | Cvalue.V.Not_based_on_null -> None, None + | LogicEvalError e -> if e <> CAlarm then Value_parameters.result ~source:(fst t.term_loc) ~once:true "@[<hov 0>Cannot evaluate@ range bound %a@ (%a). Approximating@]" - Printer.pp_term result pretty_logic_evaluation_error e; - `Approx, `Approx + Printer.pp_term term pretty_logic_evaluation_error e; + None, None in - let min_under, min_over = eval_bound `MinusInf max min otlow in - let max_under, max_over = eval_bound `PlusInf min max othigh in - let to_bound = function - | `Finite x -> Some x - | `PlusInf | `MinusInf | `Approx -> None - in - let eunder = match (min_under, max_under) with - | `Approx, _ | _, `Approx -> Cvalue.V.bottom - | (`MinusInf | `Finite _), (`PlusInf | `Finite _) -> - Cvalue.V.inject_ival - (Ival.inject_range (to_bound min_under) (to_bound max_under)) - in - let eover = - Cvalue.V.inject_ival - (Ival.inject_range (to_bound min_over) (to_bound max_over)) + let min_over, min_under = min_max opt_low + and max_under, max_over = min_max opt_high in + let eover = Cvalue.V.inject_ival (Ival.inject_range min_over max_over) in + (* Beware: [None] stands for positive infinity for [min_under] and for + negative infiny for [max_under] (in which case eunder must be bottom), + except when the bound was itself [None]. *) + let eunder = + if (opt_low <> None && min_under = None) + || (opt_high <> None && max_under = None) + then Cvalue.V.bottom + else Cvalue.V.inject_ival (Ival.inject_range min_under max_under) in + let empty = Cvalue.V.is_bottom eunder in { ldeps = !deps; etype = Cil.intType; - eunder; eover } + eunder; eover; empty; } | TCastE (typ, t) -> let r = eval_term ~alarm_mode env t in @@ -919,7 +924,8 @@ let rec eval_term ~alarm_mode env t = then cast_to_bool r else let eover = cast ~src_typ:r.etype ~dst_typ:typ r.eover in - { etype = typ; ldeps = r.ldeps; eunder = under_from_over eover; eover } + { etype = typ; ldeps = r.ldeps; eunder = under_from_over eover; eover; + empty = r.empty; } | Tif (tcond, ttrue, tfalse) -> eval_tif eval_term Cvalue.V.join Cvalue.V.meet ~alarm_mode env @@ -934,28 +940,31 @@ let rec eval_term ~alarm_mode env t = einteger v | Tunion l -> - let eunder, eover, deps = List.fold_left - (fun (accunder, accover, accdeps) t -> + let eunder, eover, deps, empty = List.fold_left + (fun (accunder, accover, accdeps, accempty) t -> let r = eval_term ~alarm_mode env t in (Cvalue.V.link accunder r.eunder, Cvalue.V.join accover r.eover, - join_logic_deps accdeps r.ldeps)) - (Cvalue.V.bottom, Cvalue.V.bottom, empty_logic_deps) l + join_logic_deps accdeps r.ldeps, + accempty || r.empty)) + (Cvalue.V.bottom, Cvalue.V.bottom, empty_logic_deps, false) l in { etype = infer_type t.term_type; - ldeps = deps; eunder; eover } + ldeps = deps; eunder; eover; empty; } | Tempty_set -> { etype = infer_type t.term_type; ldeps = empty_logic_deps; eunder = Cvalue.V.bottom; - eover = Cvalue.V.bottom } + eover = Cvalue.V.bottom; + empty = true; } | Tnull -> { etype = Cil.voidPtrType; ldeps = empty_logic_deps; eunder = Cvalue.V.singleton_zero; - eover = Cvalue.V.singleton_zero } + eover = Cvalue.V.singleton_zero; + empty = false; } | TLogic_coerce(ltyp, t) -> let r = eval_term ~alarm_mode env t in @@ -967,19 +976,15 @@ let rec eval_term ~alarm_mode env t = || Logic_const.is_boolean_type t.term_type -> r | Ctype typ when Cil.isIntegralOrPointerType typ -> r | Lreal -> - if Logic_typing.is_integral_type t.term_type - then (* Needs to be converted to reals *) - let eover = V.cast_int_to_float Fval.Real r.eover in - { etype = Cil.longDoubleType; (** hack until logic type *) - ldeps = r.ldeps; - eunder = under_from_over eover; - eover; } - else - let eover = V.cast_float_to_float Fval.Real r.eover in - { etype = Cil.longDoubleType; (** hack until logic type *) - ldeps = r.ldeps; - eunder = under_from_over eover; - eover; } + let eover = + if Logic_typing.is_integral_type t.term_type + then V.cast_int_to_float Fval.Real r.eover + else V.cast_float_to_float Fval.Real r.eover + in + { etype = Cil.longDoubleType; (** hack until logic type *) + ldeps = r.ldeps; + eover; eunder = under_from_over eover; + empty = r.empty } | _ -> if Logic_const.is_boolean_type ltyp && Logic_typing.is_integral_type t.term_type @@ -1000,7 +1005,8 @@ let rec eval_term ~alarm_mode env t = { etype = Cil.intType; ldeps = r.ldeps; eover; - eunder = under_from_over eover } + eunder = under_from_over eover; + empty = r.empty; } | Tbase_addr (_lbl, t) -> let r = eval_term ~alarm_mode env t in @@ -1009,7 +1015,8 @@ let rec eval_term ~alarm_mode env t = { etype = Cil.charPtrType; ldeps = r.ldeps; eover; - eunder = under_from_over eover } + eunder = under_from_over eover; + empty = r.empty; } | Tblock_length (_lbl, t) -> (* TODO: take label into account for locals *) let r = eval_term ~alarm_mode env t in @@ -1042,7 +1049,8 @@ let rec eval_term ~alarm_mode env t = { etype = Cil.charPtrType; ldeps = r.ldeps; eover; - eunder = under_from_over eover } + eunder = under_from_over eover; + empty = r.empty; } | Tapp (li, labels, args) -> begin if is_known_logic_fun li.l_var_info then @@ -1116,7 +1124,7 @@ and eval_binop ~alarm_mode env op t1 t2 = let eunder = eunder_op r1.eunder r2.eunder in { etype = typ_res; ldeps = join_logic_deps r1.ldeps r2.ldeps; - eunder; eover } + eunder; eover; empty = r1.empty || r2.empty; } and eval_tlhost ~alarm_mode env lv = match lv with @@ -1125,14 +1133,16 @@ and eval_tlhost ~alarm_mode env lv = { etype = v.vtype; ldeps = empty_logic_deps; eover = loc; - eunder = under_loc_from_over loc } + eunder = under_loc_from_over loc; + empty = false; } | TResult typ -> (match env.result with | Some v -> let loc = Location_Bits.inject (Base.of_varinfo v) Ival.zero in { etype = typ; ldeps = empty_logic_deps; - eunder = loc; eover = loc } + eunder = loc; eover = loc; + empty = false; } | None -> no_result ()) | TVar ({ lv_origin = None } as tlv) -> let b, ty = c_logic_var tlv in @@ -1140,7 +1150,8 @@ and eval_tlhost ~alarm_mode env lv = { etype = ty; ldeps = empty_logic_deps; eover = loc; - eunder = under_loc_from_over loc } + eunder = under_loc_from_over loc; + empty = false; } | TMem t -> let r = eval_term ~alarm_mode env t in let tres = match Cil.unrollType r.etype with @@ -1150,7 +1161,8 @@ and eval_tlhost ~alarm_mode env lv = { etype = tres; ldeps = r.ldeps; eunder = loc_bytes_to_loc_bits r.eunder; - eover = loc_bytes_to_loc_bits r.eover } + eover = loc_bytes_to_loc_bits r.eover; + empty = r.empty; } and eval_toffset ~alarm_mode env typ toffset = match toffset with @@ -1158,7 +1170,8 @@ and eval_toffset ~alarm_mode env typ toffset = { etype = typ; ldeps = empty_logic_deps; eunder = Ival.zero; - eover = Ival.zero } + eover = Ival.zero; + empty = false; } | TIndex (idx, remaining) -> let typ_e, size = match Cil.unrollType typ with | TArray (t, size, _, _) -> t, size @@ -1193,7 +1206,7 @@ and eval_toffset ~alarm_mode env typ toffset = in { etype = offsrem.etype; ldeps = join_logic_deps idxs.ldeps offsrem.ldeps; - eunder; eover } + eunder; eover; empty = idxs.empty || offsrem.empty; } | TField (fi, remaining) -> let size_current default = @@ -1206,7 +1219,8 @@ and eval_toffset ~alarm_mode env typ toffset = { etype = offsrem.etype; ldeps = offsrem.ldeps; eover = Ival.add_int (size_current Ival.top) offsrem.eover; - eunder = Ival.add_int_under (size_current Ival.bottom) offsrem.eunder } + eunder = Ival.add_int_under (size_current Ival.bottom) offsrem.eunder; + empty = offsrem.empty; } | TModel _ -> unsupported "model fields" @@ -1217,6 +1231,7 @@ and eval_tlval ~alarm_mode env (thost, toffs) = ldeps = join_logic_deps rhost.ldeps roffset.ldeps; eunder = Location_Bits.shift_under roffset.eunder rhost.eunder; eover = Location_Bits.shift roffset.eover rhost.eover; + empty = rhost.empty || roffset.empty; } and eval_term_as_lval ~alarm_mode env t = @@ -1224,22 +1239,24 @@ and eval_term_as_lval ~alarm_mode env t = | TLval tlval -> eval_tlval ~alarm_mode env tlval | Tunion l -> - let eunder, eover, deps = List.fold_left - (fun (accunder, accover, accdeps) t -> + let eunder, eover, deps, empty = List.fold_left + (fun (accunder, accover, accdeps, accempty) t -> let r = eval_term_as_lval ~alarm_mode env t in Location_Bits.link accunder r.eunder, Location_Bits.join accover r.eover, - join_logic_deps accdeps r.ldeps - ) (Location_Bits.top, Location_Bits.bottom, empty_logic_deps) l + join_logic_deps accdeps r.ldeps, + accempty || r.empty + ) (Location_Bits.top, Location_Bits.bottom, empty_logic_deps, false) l in { etype = infer_type t.term_type; ldeps = deps; - eover; eunder } + eover; eunder; empty } | Tempty_set -> { etype = infer_type t.term_type; ldeps = empty_logic_deps; eunder = Location_Bits.bottom; - eover = Location_Bits.bottom } + eover = Location_Bits.bottom; + empty = true } | Tat (t, lab) -> ignore (env_state env lab); eval_term_as_lval ~alarm_mode { env with e_cur = lab } t @@ -1270,7 +1287,7 @@ and eval_tif : 'a. (alarm_mode:_ -> _ -> _ -> 'a eval_result) -> ('a -> 'a -> 'a let eunder = meet vtrue.eunder vfalse.eunder in { etype = vtrue.etype; ldeps = join_logic_deps vtrue.ldeps vfalse.ldeps; - eunder; eover } + eunder; eover; empty = vtrue.empty || vfalse.empty; } | true, false -> eval ~alarm_mode env ttrue | false, true -> eval ~alarm_mode env tfalse | false, false -> assert false (* a logic alarm would have been raised*) @@ -1353,7 +1370,7 @@ and eval_float_builtin_arity2 ~alarm_mode env name arg1 arg2 = in let eunder = under_from_over v in let ldeps = join_logic_deps r1.ldeps r2.ldeps in - { etype = r1.etype; eunder; eover = v; ldeps } + { etype = r1.etype; eunder; eover = v; ldeps; empty = r1.empty || r2.empty; } (* Evaluates the max (resp. the min) between the terms [t1] and [t2], according to [backward_left v1 v2] that reduces [v1] by assuming it is @@ -1366,7 +1383,7 @@ and eval_extremum etype backward_left ~alarm_mode env t1 t2 = let eover = Cvalue.V.join reduced_v1 reduced_v2 in let eunder = Cvalue.V.meet r1.eunder r2.eunder in let ldeps = join_logic_deps r1.ldeps r2.ldeps in - {eover; eunder; ldeps; etype} + {eover; eunder; ldeps; etype; empty = r1.empty || r2.empty; } let eval_tlval_as_location ~alarm_mode env t = @@ -1581,7 +1598,7 @@ let eval_forall_predicate state r test = let under_loc = make_loc r.eunder in forall_in_under_location state under_loc test | True -> True - | False -> False + | False -> if r.empty then Unknown else False (* Evaluation of an \initialized predicate on a location evaluated to [r] in the state [state]. *) @@ -2101,6 +2118,11 @@ and eval_predicate env pred = let alarm_mode = Fail in let loc = pred.pred_loc in let rec do_eval env p = + try do_eval_exn env p + with LogicEvalError ee -> + display_evaluation_error ~loc:p.pred_loc ee; + Unknown + and do_eval_exn env p = match p.pred_content with | Ptrue -> True | Pfalse -> False @@ -2145,117 +2167,100 @@ and eval_predicate env pred = | Unknown, _ | _, Unknown -> Unknown | _ -> False end - | Pat (p, lbl) -> begin - ignore (env_state env lbl); - try do_eval { env with e_cur = lbl } p - with LogicEvalError ee -> display_evaluation_error ~loc:p.pred_loc ee; Unknown - end + | Pat (p, lbl) -> + ignore (env_state env lbl); + do_eval { env with e_cur = lbl } p - | Pvalid (_label, tsets) | Pvalid_read (_label, tsets) -> begin - (* TODO: see same constructor in reduce_by_predicate *) - try - let access = - match p.pred_content with Pvalid_read _ -> Read | _ -> Write - in - let state = env_current_state env in - let typ_pointed = Logic_typing.ctype_of_pointed tsets.term_type in - (* Check if we are trying to write in a const l-value *) - if access = Write && Value_util.is_const_write_invalid typ_pointed then - raise Stop; - let size = Eval_typ.sizeof_lval_typ typ_pointed in - (* Check that the given location is valid *) - let valid ~over:locbytes_over ~under:locbytes_under = - let loc = loc_bytes_to_loc_bits locbytes_over in - let loc = Locations.make_loc loc size in - if not Locations.(is_valid access loc) then ( - (* \valid does not hold if the over-approximation is invalid - everywhere, or if a part of the under-approximation is invalid - *) - let valid = valid_part access loc in - if Locations.is_bottom_loc valid then raise Stop; - let loc_under = loc_bytes_to_loc_bits locbytes_under in - let loc_under = Locations.make_loc loc_under size in - let valid_loc_under = - Locations.valid_part access loc_under - in - if not (Location.equal loc_under valid_loc_under) then - raise Stop; - raise DoNotReduce (* In any case *)) - in - (match tsets.term_node with - | TLval _ -> - (* Evaluate the left-value, and check that it is initialized - and not an escaping pointer *) - let loc = eval_tlval_as_location ~alarm_mode env tsets in - if not Locations.(is_valid Read loc) then - c_alarm (); - let v = Model.find_indeterminate state loc in - let v, ok = match v with - | Cvalue.V_Or_Uninitialized.C_uninit_esc v - | Cvalue.V_Or_Uninitialized.C_uninit_noesc v - | Cvalue.V_Or_Uninitialized.C_init_esc v -> v, false - | Cvalue.V_Or_Uninitialized.C_init_noesc v -> v, true - in - if Cvalue.V.is_bottom v && not ok then raise Stop; - valid ~over:v ~under:V.bottom (*No precise under-approximation*); - if not ok then raise DoNotReduce - | _ -> - let v = eval_term ~alarm_mode env tsets in - valid ~over:v.eover ~under:v.eunder - ); - True - with - | DoNotReduce -> Unknown - | LogicEvalError ee -> display_evaluation_error ~loc:p.pred_loc ee; Unknown - | Stop -> False - end + | Pvalid (_label, tsets) | Pvalid_read (_label, tsets) -> + (* TODO: see same constructor in reduce_by_predicate *) + let kind = match p.pred_content with Pvalid_read _ -> Read | _ -> Write in + let typ_pointed = Logic_typing.ctype_of_pointed tsets.term_type in + (* Check if we are trying to write in a const l-value *) + if kind = Write && Value_util.is_const_write_invalid typ_pointed + then False + else + let eover, eunder, indeterminate, empty = + match tsets.term_node with + | TLval tlval -> + (* Do not use [eval_term]: the evaluation would fail if the value of + [tlval] is uninitialized or escaping. *) + let r = eval_tlval ~alarm_mode env tlval in + let loc = make_loc r.eover (Eval_typ.sizeof_lval_typ r.etype) in + let state = env_current_state env in + let v = find_indeterminate ~alarm_mode state loc in + let v, indeterminate = + Cvalue.V_Or_Uninitialized.(get_v v, is_indeterminate v) + in + v, Cvalue.V.bottom, indeterminate, r.empty + | _ -> + let result = eval_term ~alarm_mode env tsets in + result.eover, result.eunder, false, result.empty + in + let status = + (* [True] on empty sets, [False] on bottom locations otherwise. *) + if Cvalue.V.is_bottom eover + then if empty then True else False + else + let size = Eval_typ.sizeof_lval_typ typ_pointed in + let make_loc l = make_loc (loc_bytes_to_loc_bits l) size in + let loc_over = make_loc eover in + (* The predicate holds if [eover] is entirely valid. It is false if + [eover] is entirely invalid or if [eunder] contains an invalid + location. Unknown otherwise. *) + if Locations.is_valid kind loc_over + then True + else if Locations.is_bottom_loc (valid_part kind loc_over) + || contains_invalid_loc kind (make_loc eunder) + then False + else Unknown + in + (* False status on uninitialized or escaping pointers. *) + let status = + if indeterminate then join_predicate_status status False else status + in + (* True status on empty sets. *) + if empty then join_predicate_status status True else status | Pvalid_function tsets -> begin - try - let v = eval_term ~alarm_mode env tsets in - let funs, warn = Main_values.CVal.resolve_functions v.eover in - match funs with - | `Top -> Unknown - | `Value funs -> - let typ = Cil.typeOf_pointed v.etype in - let funs, warn' = Eval_typ.compatible_functions typ funs in - if warn || warn' then - (* No function possible -> signal hard error. Otherwise, follow - Eva's convention, which is not to stop on semi-ok functions. *) - if funs = [] then False else Unknown - else - True - with - | LogicEvalError ee -> display_evaluation_error ~loc:p.pred_loc ee; Unknown + let v = eval_term ~alarm_mode env tsets in + let funs, warn = Main_values.CVal.resolve_functions v.eover in + match funs with + | `Top -> Unknown + | `Value funs -> + let typ = Cil.typeOf_pointed v.etype in + let funs, warn' = Eval_typ.compatible_functions typ funs in + if warn || warn' then + (* No function possible -> signal hard error. Otherwise, follow + Eva's convention, which is not to stop on semi-ok functions. *) + if funs = [] then False else Unknown + else + True end | Pinitialized (label,tsets) | Pdangling (label,tsets) -> begin - try - (* Create [*tsets] and compute its location. This is important in - case [tsets] points to the address of a bitfield, which we - cannot evaluate as a pointer (indexed on bytes) *) - let star_tsets = deref_tsets tsets in - let locb = eval_tlval ~alarm_mode env star_tsets in - let state = env_state env label in - match p.pred_content with - | Pinitialized _ -> eval_initialized state locb - | Pdangling _ -> eval_dangling state locb - | _ -> assert false - with - | LogicEvalError ee -> display_evaluation_error ~loc:p.pred_loc ee; Unknown + (* Create [*tsets] and compute its location. This is important in + case [tsets] points to the address of a bitfield, which we + cannot evaluate as a pointer (indexed on bytes) *) + let star_tsets = deref_tsets tsets in + let locb = eval_tlval ~alarm_mode env star_tsets in + let state = env_state env label in + match p.pred_content with + | Pinitialized _ -> eval_initialized state locb + | Pdangling _ -> eval_dangling state locb + | _ -> assert false end - | Prel (op,t1,t2) -> begin - try - let r = eval_binop ~alarm_mode env (lop_to_cop op) t1 t2 in - if V.equal V.singleton_zero r.eover - then False - else if V.equal V.singleton_one r.eover - then True - else Unknown - with - | LogicEvalError ee -> display_evaluation_error ~loc:p.pred_loc ee; Unknown - end + | Prel (op,t1,t2) -> + let r = eval_binop ~alarm_mode env (lop_to_cop op) t1 t2 in + (* [eval_binop] uses the forward semantics of [Cvalue.V], which does not + handle empty sets. Returns [Unknown] if empty sets are possible. *) + if r.empty + then Unknown + else if V.equal V.singleton_zero r.eover + then False + else if V.equal V.singleton_one r.eover + then True + else Unknown | Pforall (varl, p') | Pexists (varl, p') -> begin @@ -2266,8 +2271,7 @@ and eval_predicate env pred = | Pexists _ -> if r = False then False else Unknown | Pforall _ -> if r = True then True else Unknown | _ -> assert false - with - | LogicEvalError _ee -> (*display_evaluation_error ~loc ee;*) Unknown + with LogicEvalError _ee -> Unknown (* No error display? *) end | Pnot p -> begin match do_eval env p with @@ -2314,8 +2318,7 @@ and eval_predicate env pred = aux lz; if !unknown then Unknown else True with - | Exit -> False - | LogicEvalError ee -> display_evaluation_error ~loc:p.pred_loc ee; Unknown) + | Exit -> False) | Papp (li, labels, args) -> begin if is_known_predicate li.l_var_info then @@ -2325,6 +2328,7 @@ and eval_predicate env pred = | None -> Unknown | Some p' -> do_eval env p' end + | Pif (tcond, ptrue, pfalse) -> begin let r = eval_term ~alarm_mode env tcond in @@ -2357,7 +2361,6 @@ and eval_predicate env pred = unary_fun (V.project_float eval_result.eover) with | V.Not_based_on_null -> Unknown - | LogicEvalError ee -> display_evaluation_error ~loc ee; Unknown in let fval_cmp comp arg1 arg2 = try @@ -2368,7 +2371,6 @@ and eval_predicate env pred = Fval.forward_comp comp f1 f2 with | V.Not_based_on_null -> Unknown - | LogicEvalError ee -> display_evaluation_error ~loc ee; Unknown in match li.l_var_info.lv_name, args with | "\\is_finite", [arg] -> unary_float Fval.is_finite arg @@ -2393,59 +2395,41 @@ and eval_predicate env pred = Value_parameters.abort "Wrong argument: \\warning expects a constant string" end - | "\\subset", [argl;argr] -> begin - try - let l = eval_term ~alarm_mode env argl in - let r = eval_term ~alarm_mode env argr in - if V.is_included l.eover r.eunder then - True (* all elements of [l] are included in the guaranteed elements - of [r] *) - else if not (V.is_included l.eunder r.eover) || - not (V.intersects l.eover r.eover) - then False (* one guaranteed element of [l] is not included in [r], - or [l] and [r] are disjoint, in which case there is - an element of [l] not in [r]. (Here, [l] is not bottom, - as [V.is_included bottom r.eunder] holds. *) - else Unknown - with - | LogicEvalError ee -> display_evaluation_error ~loc ee; Unknown - end - | "valid_read_string", [arg] -> begin - try - let r = eval_term ~alarm_mode env arg in - eval_valid_read_str ~wide:false env r.eover - with LogicEvalError ee -> display_evaluation_error ~loc ee; Unknown - end - | "valid_string", [arg] -> begin - try - let r = eval_term ~alarm_mode env arg in - eval_valid_str ~wide:false env r.eover - with LogicEvalError ee -> display_evaluation_error ~loc ee; Unknown - end - | "valid_read_wstring", [arg] -> begin - try - let r = eval_term ~alarm_mode env arg in - eval_valid_read_str ~wide:true env r.eover - with LogicEvalError ee -> display_evaluation_error ~loc ee; Unknown - end - | "valid_wstring", [arg] -> begin - try - let r = eval_term ~alarm_mode env arg in - eval_valid_str ~wide:true env r.eover - with LogicEvalError ee -> display_evaluation_error ~loc ee; Unknown - end - | "is_allocable", [arg] when comes_from_fc_stdlib li.l_var_info -> begin - try - let r = eval_term ~alarm_mode env arg in - eval_is_allocable r.eover - with LogicEvalError ee -> display_evaluation_error ~loc ee; Unknown - end + | "\\subset", [argl;argr] -> + let l = eval_term ~alarm_mode env argl in + let r = eval_term ~alarm_mode env argr in + if r.empty then + if V.is_bottom l.eover then True + else if not (V.is_bottom l.eunder || l.empty) then False + else Unknown + else if V.is_included l.eover r.eunder then + True (* all elements of [l] are included in the guaranteed elements + of [r] *) + else if not (V.is_included l.eunder r.eover) || + not (V.intersects l.eover r.eover) + then False (* one guaranteed element of [l] is not included in [r], + or [l] and [r] are disjoint, in which case there is + an element of [l] not in [r]. (Here, [l] is not bottom, + as [V.is_included bottom r.eunder] holds. *) + else Unknown + | "valid_read_string", [arg] -> + let r = eval_term ~alarm_mode env arg in + eval_valid_read_str ~wide:false env r.eover + | "valid_string", [arg] -> + let r = eval_term ~alarm_mode env arg in + eval_valid_str ~wide:false env r.eover + | "valid_read_wstring", [arg] -> + let r = eval_term ~alarm_mode env arg in + eval_valid_read_str ~wide:true env r.eover + | "valid_wstring", [arg] -> + let r = eval_term ~alarm_mode env arg in + eval_valid_str ~wide:true env r.eover + | "is_allocable", [arg] when comes_from_fc_stdlib li.l_var_info -> + let r = eval_term ~alarm_mode env arg in + eval_is_allocable r.eover | _, _ -> assert false in - try (* Each case of the matching above should handle evaluation errors. - This is just an additional security. *) - do_eval env pred - with LogicEvalError ee -> display_evaluation_error ~loc ee; Unknown + do_eval env pred (* -------------------------------------------------------------------------- *) diff --git a/src/plugins/value/legacy/eval_terms.mli b/src/plugins/value/legacy/eval_terms.mli index 9a134a1b1aef75bc9c34cff581fb72490d9959d5..8a508710513fa7d5276a8e8ebbcded81303dce08 100644 --- a/src/plugins/value/legacy/eval_terms.mli +++ b/src/plugins/value/legacy/eval_terms.mli @@ -90,6 +90,7 @@ type 'a eval_result = { etype: Cil_types.typ; eunder: 'a; eover: 'a; + empty: bool; ldeps: logic_deps; } diff --git a/tests/builtins/oracle/memcpy_invalid.res.oracle b/tests/builtins/oracle/memcpy_invalid.res.oracle index 3937aa395d74bcc69d78bed77ab83b172a787905..6863c287b84c64f471434b796daa2e840900780a 100644 --- a/tests/builtins/oracle/memcpy_invalid.res.oracle +++ b/tests/builtins/oracle/memcpy_invalid.res.oracle @@ -14,12 +14,13 @@ [eva:alarm] tests/builtins/memcpy_invalid.c:17: Warning: function memcpy: precondition 'valid_dest' got status unknown. [eva:alarm] tests/builtins/memcpy_invalid.c:17: Warning: - function memcpy: precondition 'valid_src' got status invalid. + function memcpy: precondition 'valid_src' got status unknown. [eva] tests/builtins/memcpy_invalid.c:17: - function memcpy: no state left, precondition 'separation' got status valid. + function memcpy: precondition 'separation' got status valid. +[eva] share/libc/string.h:98: + cannot evaluate ACSL term, unsupported ACSL construct: logic function memcmp [eva] Recording results for test [from] Computing for function test -[from] Non-terminating function test (no dependencies) [from] Done for function test [eva] Done for function test [eva] computing for function test <- main. @@ -30,7 +31,6 @@ [eva] tests/builtins/memcpy_invalid.c:17: Call to builtin memcpy [eva] Recording results for test [from] Computing for function test -[from] Non-terminating function test (no dependencies) [from] Done for function test [eva] Done for function test [eva] Recording results for main @@ -39,7 +39,9 @@ [eva] done for function main [eva] ====== VALUES COMPUTED ====== [eva:final-states] Values at end of function test: - NON TERMINATING FUNCTION + dst ∈ {0} + src ∈ {0} + i ∈ [0..16],0%2 [eva:final-states] Values at end of function main: __retres ∈ {0} [from] ====== DISPLAYING CALLWISE DEPENDENCIES ====== @@ -49,9 +51,9 @@ dst FROM src \result FROM dest [from] call to test at tests/builtins/memcpy_invalid.c:22 (by main): - NON TERMINATING - NO EFFECTS + NO EFFECTS [from] call to test at tests/builtins/memcpy_invalid.c:23 (by main): - NON TERMINATING - NO EFFECTS + NO EFFECTS [from] entry point: \result FROM \nothing [from] ====== END OF CALLWISE DEPENDENCIES ====== diff --git a/tests/value/oracle/empty_union.res.oracle b/tests/value/oracle/empty_union.res.oracle index a4744572240aff5dfcc27b4905b6afe7f96e2aad..81eff46197f3d00015306a96bb08116a75fab389 100644 --- a/tests/value/oracle/empty_union.res.oracle +++ b/tests/value/oracle/empty_union.res.oracle @@ -39,8 +39,8 @@ [eva] Done for function f [eva] computing for function copy_empty <- main. Called from tests/value/empty_union.c:81. -[eva] tests/value/empty_union.c:37: - function copy_empty: postcondition got status valid. +[eva:alarm] tests/value/empty_union.c:37: Warning: + function copy_empty: postcondition got status unknown. [eva] Recording results for copy_empty [eva] Done for function copy_empty [eva] tests/value/empty_union.c:83: Frama_C_show_each_res: {74} diff --git a/tests/value/oracle/logic.res.oracle b/tests/value/oracle/logic.res.oracle index 1920a84df5ef397cf97a49829ce57f1d79f6f942..99b0604e3a83ad03a8dd2fe61601cb152b69111a 100644 --- a/tests/value/oracle/logic.res.oracle +++ b/tests/value/oracle/logic.res.oracle @@ -38,7 +38,7 @@ [eva:alarm] tests/value/logic.c:127: Warning: assertion got status unknown. [eva] tests/value/logic.c:128: assertion got status valid. [eva] tests/value/logic.c:130: assertion got status valid. -[eva] tests/value/logic.c:131: assertion got status valid. +[eva:alarm] tests/value/logic.c:131: Warning: assertion got status unknown. [eva:alarm] tests/value/logic.c:132: Warning: assertion got status unknown. [eva] tests/value/logic.c:134: cannot evaluate ACSL term, unsupported ACSL construct: == operation on non-supported type struct ts