diff --git a/Makefile b/Makefile index dd8b0234365cedef0c3d91c5f996c5e09fd151ae..d8e4d6405e58be280bce04571ebebad065f92984 100644 --- a/Makefile +++ b/Makefile @@ -860,9 +860,10 @@ endif # General rules for ordering files within PLUGIN_CMO: # - try to keep the legacy Value before Eva PLUGIN_CMO:= partitioning/split_strategy value_parameters \ - utils/value_perf utils/value_util utils/red_statuses \ + utils/value_perf utils/eva_annotations \ + utils/value_util utils/red_statuses \ utils/mark_noresults \ - utils/widen_hints_ext utils/widen utils/partitioning_annots \ + utils/widen_hints_ext utils/widen \ partitioning/split_return \ partitioning/per_stmt_slevel \ utils/library_functions \ diff --git a/headers/header_spec.txt b/headers/header_spec.txt index ad7a910447dba8edf011c15b02746bd9e3a48721..3e89f12518e8ee6a37d47076c045a8dd2c137848 100644 --- a/headers/header_spec.txt +++ b/headers/header_spec.txt @@ -1328,8 +1328,8 @@ src/plugins/value/utils/red_statuses.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/library_functions.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/library_functions.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/mark_noresults.ml: CEA_LGPL_OR_PROPRIETARY -src/plugins/value/utils/partitioning_annots.ml: CEA_LGPL_OR_PROPRIETARY -src/plugins/value/utils/partitioning_annots.mli: CEA_LGPL_OR_PROPRIETARY +src/plugins/value/utils/eva_annotations.ml: CEA_LGPL_OR_PROPRIETARY +src/plugins/value/utils/eva_annotations.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/state_import.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/state_import.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/structure.ml: CEA_LGPL_OR_PROPRIETARY diff --git a/src/plugins/value/engine/evaluation.ml b/src/plugins/value/engine/evaluation.ml index 699f57b6e2443d8cbf912d37c306f30ddd3df2ba..c9ecc4766a4be390c9a5f677e1cc1e7388398b7d 100644 --- a/src/plugins/value/engine/evaluation.ml +++ b/src/plugins/value/engine/evaluation.ml @@ -196,20 +196,21 @@ module type S = sig and type origin = origin and type loc = loc val evaluate : - ?valuation:Valuation.t -> ?reduction:bool -> + ?valuation:Valuation.t -> ?reduction:bool -> ?subdivnb:int -> state -> exp -> (Valuation.t * value) evaluated val copy_lvalue : - ?valuation:Valuation.t -> + ?valuation:Valuation.t -> ?subdivnb:int -> state -> lval -> (Valuation.t * value flagged_value) evaluated val lvaluate : - ?valuation:Valuation.t -> for_writing:bool -> + ?valuation:Valuation.t -> ?subdivnb:int -> for_writing:bool -> state -> lval -> (Valuation.t * loc * typ) evaluated val reduce: ?valuation:Valuation.t -> state -> exp -> bool -> Valuation.t evaluated val assume: ?valuation:Valuation.t -> state -> exp -> value -> Valuation.t or_bottom val eval_function_exp: - exp -> ?args:exp list -> state -> (Kernel_function.t * Valuation.t) list evaluated + ?subdivnb:int -> exp -> ?args:exp list -> state -> + (Kernel_function.t * Valuation.t) list evaluated val interpret_truth: alarm:(unit -> Alarms.t) -> 'a -> 'a Abstract_value.truth -> 'a evaluated end @@ -741,21 +742,35 @@ module Make Forward Evaluation ------------------------------------------------------------------------ *) - (* Makes the oracle for the domain queries, called by the forward evaluation. - Defined below, after applying the subdivided_evaluation to the forward - evaluation function. *) - let make_oracle = ref (fun _ _ _ -> `Value Value.top, Alarmset.all) + (* The forward evaluation context: arguments that must be passed through + the mutually recursive evaluation functions without being modified. *) + type context = + { (* The abstract domain state in which the evaluation takes place. *) + state: Domain.t; + (* Maximum number of subdivisions. See {!Subdivided_evaluation} for + more details. *) + subdivision: int; + (* The remaining fuel: maximum number of nested oracle uses, decremented + at each call to the oracle. *) + remaining_fuel: int; + (* The oracle which can be used by abstract domains to get a value for + some expressions. *) + oracle: context -> exp -> Value.t evaluated; + } + + (* Builds the oracle from the context. *) + let make_oracle context = context.oracle context (* Returns the cached value and alarms for the evaluation if it exists; call [coop_forward_eval] and caches its result otherwise. Also returns a boolean indicating whether the expression is volatile. *) - let rec root_forward_eval fuel state expr = + let rec root_forward_eval context expr = (* Search in the cache for the result of a previous computation. *) try let record, report = Cache.find' !cache expr in (* If the record was computed with more fuel than [fuel], return it. *) if report.fuel = Loop then fuel_consumed := true; - if less_fuel_than fuel report.fuel + if less_fuel_than context.remaining_fuel report.fuel then (record.value.v >>-: fun v -> v, report.volatile), record.val_alarms else raise Not_found (* If no result found, evaluate the expression. *) @@ -766,7 +781,7 @@ module Make (* Fill the cache to avoid loops in the use of the oracle. *) cache := Cache.add' !cache expr (top_record, dummy_report); (* Evaluation of [expr]. *) - let result, alarms = coop_forward_eval fuel state expr in + let result, alarms = coop_forward_eval context expr in let value = result >>- fun (record, reduction, volatile) -> (* Put the alarms in the record. *) @@ -774,6 +789,7 @@ module Make (* Inter-reduction of the value (in case of a reduced product). *) let record = reduce_value record in (* Cache the computed result with an appropriate report. *) + let fuel = context.remaining_fuel in let fuel = if !fuel_consumed then Finite fuel else Infty in let report = {fuel; reduction; volatile} in cache := Cache.add' !cache expr (record, report); @@ -783,7 +799,7 @@ module Make fuel_consumed := previous_fuel_consumed || !fuel_consumed; value, alarms - and forward_eval fuel state expr = root_forward_eval fuel state expr >>=: fst + and forward_eval context expr = root_forward_eval context expr >>=: fst (* The functions below returns, along with the computed value (when it is not bottom): @@ -794,12 +810,13 @@ module Make (* Asks the abstract domain for abstractions (value and alarms) of [expr], and performs the narrowing with the abstractions computed by [internal_forward_eval]. *) - and coop_forward_eval fuel state expr = + and coop_forward_eval context expr = match expr.enode with - | Lval lval -> eval_lval fuel state lval + | Lval lval -> eval_lval context lval | BinOp _ | UnOp _ | CastE _ -> begin - let intern_value, alarms = internal_forward_eval fuel state expr in - let oracle = !make_oracle fuel state in + let intern_value, alarms = internal_forward_eval context expr in + let state = context.state in + let oracle = make_oracle context in let domain_value, alarms' = Domain.extract_expr oracle state expr in (* Intersection of alarms, as each sets of alarms are correct and "complete" for the evaluation of [expr]. *) @@ -828,7 +845,7 @@ module Make v, alarms end | _ -> - internal_forward_eval fuel state expr + internal_forward_eval context expr >>=: fun (value, reduction, volatile) -> let value = define_value value and origin = None @@ -837,39 +854,39 @@ module Make reduction, volatile (* Recursive descent in the sub-expressions. *) - and internal_forward_eval fuel state expr = + and internal_forward_eval context expr = let compute_reduction (v, a) volatile = (v, a) >>=: fun v -> let reduction = if Alarmset.is_empty a then Neither else Forward in v, reduction, volatile in match expr.enode with - | Info (e, _) -> internal_forward_eval fuel state e - | Const constant -> internal_forward_eval_constant fuel state expr constant + | Info (e, _) -> internal_forward_eval context e + | Const constant -> internal_forward_eval_constant context expr constant | Lval _lval -> assert false | AddrOf v | StartOf v -> - lval_to_loc fuel ~for_writing:false ~reduction:false state v + lval_to_loc context ~for_writing:false ~reduction:false v >>=: fun (loc, _, _) -> Loc.to_value loc, Neither, false | UnOp (op, e, typ) -> - root_forward_eval fuel state e >>= fun (v, volatile) -> + root_forward_eval context e >>= fun (v, volatile) -> forward_unop op (e, v) >>= fun v -> let may_overflow = op = Neg in let v = handle_overflow ~may_overflow expr typ v in compute_reduction v volatile | BinOp (op, e1, e2, typ) -> - root_forward_eval fuel state e1 >>= fun (v1, volatile1) -> - root_forward_eval fuel state e2 >>= fun (v2, volatile2) -> + root_forward_eval context e1 >>= fun (v1, volatile1) -> + root_forward_eval context e2 >>= fun (v2, volatile2) -> forward_binop typ (e1, v1) op (e2, v2) >>= fun v -> let may_overflow = may_overflow op in let v = handle_overflow ~may_overflow expr typ v in compute_reduction v (volatile1 || volatile2) | CastE (dst, e) -> - root_forward_eval fuel state e >>= fun (value, volatile) -> + root_forward_eval context e >>= fun (value, volatile) -> let v = forward_cast ~dst e value in let v = match Cil.unrollType dst with | TFloat (fkind, _) -> v >>= remove_special_float expr fkind @@ -882,9 +899,9 @@ module Make | Some v -> return (Value.inject_int (Cil.typeOf expr) v, Neither, false) | _ -> return (Value.top_int, Neither, false) - and internal_forward_eval_constant fuel state expr constant = + and internal_forward_eval_constant context expr constant = let eval = match constant with - | CEnum {eival = e} -> forward_eval fuel state e + | CEnum {eival = e} -> forward_eval context e | CReal (_f, fkind, _fstring) -> let value = Value.constant expr constant in remove_special_float expr fkind value @@ -907,13 +924,14 @@ module Make If the location is not bottom, the function also returns the typ of the lvalue, and a boolean indicating that the lvalue contains a sub-expression with volatile qualifier (in its host or offset). *) - and lval_to_loc fuel ~for_writing ~reduction state lval = + and lval_to_loc context ~for_writing ~reduction lval = let compute () = let res, alarms = - reduced_lval_to_loc fuel ~for_writing ~reduction state lval + reduced_lval_to_loc context ~for_writing ~reduction lval in let res = res >>-: fun (loc, typ_offs, red, volatile) -> + let fuel = context.remaining_fuel in let record = { loc; typ = typ_offs; loc_alarms = alarms } and report = { fuel = Finite fuel; reduction = red; volatile } and loc_report = { for_writing; with_reduction = reduction } in @@ -926,15 +944,15 @@ module Make | `Value (record, (report, loc_report)) -> if already_precise_loc_report ~for_writing ~reduction loc_report - && less_fuel_than fuel report.fuel + && less_fuel_than context.remaining_fuel report.fuel then `Value (record.loc, record.typ, report.volatile), record.loc_alarms else compute () | `Top -> compute () (* If [reduction] is false, don't reduce the location and the offset by their valid parts, and don't emit alarms about their validity. *) - and reduced_lval_to_loc fuel ~for_writing ~reduction state lval = - internal_lval_to_loc fuel ~for_writing ~reduction state lval + and reduced_lval_to_loc context ~for_writing ~reduction lval = + internal_lval_to_loc context ~for_writing ~reduction lval >>= fun (loc, typ, volatile) -> if not reduction then `Value (loc, typ, Neither, volatile), Alarmset.none @@ -954,13 +972,13 @@ module Make (* Internal evaluation of a lvalue to an abstract location. Combination of the evaluation of the right part of an lval (an host) with an offset, to obtain a location *) - and internal_lval_to_loc fuel ~for_writing ~reduction state lval = + and internal_lval_to_loc context ~for_writing ~reduction lval = let host, offset = lval in let typ = match host with | Var host -> host.vtype | Mem x -> Cil.typeOf_pointed (Cil.typeOf x) in - eval_offset fuel ~reduce_valid_index:reduction typ state offset + eval_offset context ~reduce_valid_index:reduction typ offset >>= fun (offs, typ_offs, offset_volatile) -> if for_writing && Value_util.is_const_write_invalid typ_offs then @@ -968,33 +986,33 @@ module Make Alarmset.singleton ~status:Alarmset.False (Alarms.Memory_access (lval, Alarms.For_writing)) else - eval_host fuel state typ_offs offs host >>=: fun (loc, host_volatile) -> + eval_host context typ_offs offs host >>=: fun (loc, host_volatile) -> loc, typ_offs, offset_volatile || host_volatile (* Host evaluation. Also returns a boolean which is true if the host contains a volatile sub-expression. *) - and eval_host fuel state typ_offset offs = function + and eval_host context typ_offset offs = function | Var host -> (Loc.forward_variable typ_offset host offs >>-: fun loc -> loc, false), Alarmset.none | Mem x -> - root_forward_eval fuel state x >>=. fun (loc_lv, volatile) -> + root_forward_eval context x >>=. fun (loc_lv, volatile) -> Loc.forward_pointer typ_offset loc_lv offs >>-: fun loc -> loc, volatile (* Offset evaluation. Also returns a boolean which is true if the offset contains a volatile sub-expression. *) - and eval_offset fuel ~reduce_valid_index typ state = function - | NoOffset -> return (Loc.no_offset, typ, false) + and eval_offset context ~reduce_valid_index typ = function + | NoOffset -> return (Loc.no_offset, typ, false) | Index (index_expr, remaining) -> let typ_pointed, array_size = match Cil.unrollType typ with | TArray (t, size, _, _) -> t, size | t -> Value_parameters.fatal ~current:true "Got type '%a'" Printer.pp_typ t in - eval_offset fuel ~reduce_valid_index typ_pointed state remaining >>= + eval_offset context ~reduce_valid_index typ_pointed remaining >>= fun (roffset, typ_offs, remaining_volatile) -> - root_forward_eval fuel state index_expr >>= fun (index, volatile) -> + root_forward_eval context index_expr >>= fun (index, volatile) -> let valid_index = if not (Kernel.SafeArrays.get ()) || not reduce_valid_index then `Value index, Alarmset.none @@ -1018,14 +1036,14 @@ module Make | Field (fi, remaining) -> let attrs = Cil.filter_qualifier_attributes (Cil.typeAttrs typ) in let typ_fi = Cil.typeAddAttributes attrs fi.ftype in - eval_offset fuel ~reduce_valid_index typ_fi state remaining + eval_offset context ~reduce_valid_index typ_fi remaining >>=: fun (r, typ_res, volatile) -> let off = Loc.forward_field typ fi r in off, typ_res, volatile - and eval_lval ?(indeterminate=false) fuel state lval = + and eval_lval ?(indeterminate=false) context lval = (* Computes the location of [lval]. *) - lval_to_loc fuel ~for_writing:false ~reduction:true state lval + lval_to_loc context ~for_writing:false ~reduction:true lval >>= fun (loc, typ_lv, volatile_expr) -> let typ_lv = Cil.unrollType typ_lv in (* the lvalue is volatile: @@ -1035,8 +1053,8 @@ module Make *) let volatile = volatile_expr || Cil.typeHasQualifier "volatile" typ_lv in (* Find the value of the location, if not bottom. *) - let oracle = !make_oracle fuel state in - let v, alarms = Domain.extract_lval oracle state lval typ_lv loc in + let oracle = make_oracle context in + let v, alarms = Domain.extract_lval oracle context.state lval typ_lv loc in let alarms = close_dereference_alarms lval alarms in if indeterminate then @@ -1062,35 +1080,55 @@ module Make (* These two modules could be implemented as mutually recursive, to avoid the reference for the oracle given to the domains. *) module Forward_Evaluation = struct - type state = Domain.t - let evaluate ?(valuation=Cache.empty) ~fuel state expr = + type nonrec context = context + let evaluate context valuation expr = cache := valuation; - root_forward_eval fuel state expr >>=: fun (value, _) -> + root_forward_eval context expr >>=: fun (value, _) -> !cache, value end module Subdivided_Evaluation = Subdivided_evaluation.Make (Value) (Loc) (Cache) (Forward_Evaluation) - let () = - make_oracle := - fun fuel state -> - let fuel = pred fuel in - if fuel > 0 - then - fun expr -> - let valuation = !cache in - Subdivided_Evaluation.evaluate ~valuation ~fuel state expr - >>=: fun (valuation, value) -> - cache := valuation; - value - else - fun _ -> fuel_consumed := true; `Value Value.top, Alarmset.all + let oracle context = + let remaining_fuel = pred context.remaining_fuel in + if remaining_fuel > 0 + then + fun expr -> + let valuation = !cache in + let context = { context with remaining_fuel } in + let subdivnb = context.subdivision in + Subdivided_Evaluation.evaluate context valuation ~subdivnb expr + >>=: fun (valuation, value) -> + cache := valuation; + value + else + fun _ -> fuel_consumed := true; `Value Value.top, Alarmset.all + + (* Context for the forward evaluation of a root expression in state [state] + with maximal precision. *) + let root_context ?subdivnb state = + let remaining_fuel = root_fuel () in + (* By default, use the number of subdivision defined by the global option + -eva-subdivide-non-linear. *) + let subdivision = + match subdivnb with + | None -> Value_parameters.LinearLevel.get () + | Some n -> n + in + { state; subdivision; remaining_fuel; oracle } - let subdivided_forward_eval valuation state expr = - let fuel = root_fuel () in - Subdivided_Evaluation.evaluate ~valuation ~fuel state expr + (* Context for a fast forward evaluation with minimal precision: + no subdivisions and no calls to the oracle. *) + let fast_eval_context state = + let remaining_fuel = no_fuel in + let subdivision = 0 in + { state; subdivision; remaining_fuel; oracle } + let subdivided_forward_eval valuation ?subdivnb state expr = + let context = root_context ?subdivnb state in + let subdivnb = context.subdivision in + Subdivided_Evaluation.evaluate context valuation ~subdivnb expr (* ------------------------------------------------------------------------ Backward Evaluation @@ -1122,7 +1160,7 @@ module Make let evaluate_for_reduction state expr = try `Value (Cache.find' !cache expr) with Not_found -> - fst (forward_eval no_fuel state expr) >>-: fun _ -> + fst (forward_eval (fast_eval_context state) expr) >>-: fun _ -> try Cache.find' !cache expr with Not_found -> assert false @@ -1314,7 +1352,8 @@ module Make | _ -> let reduce_valid_index = true in let typ_lval = Cil.typeOf_pointed (Cil.typeOf expr) in - fst (eval_offset no_fuel ~reduce_valid_index typ_lval state offset) + let context = fast_eval_context state in + fst (eval_offset context ~reduce_valid_index typ_lval offset) >>- fun (loc_offset, _, _) -> find_val expr >>- fun value -> Loc.backward_pointer value loc_offset location @@ -1330,7 +1369,8 @@ module Make | Index (exp, remaining) -> find_val exp >>- fun v -> let typ_pointed = Cil.typeOf_array_elem typ in - fst (eval_offset no_fuel ~reduce_valid_index:true typ_pointed state remaining) + let context = fast_eval_context state in + fst (eval_offset context ~reduce_valid_index:true typ_pointed remaining) >>- fun (rem, _, _) -> Loc.backward_index typ_pointed v rem loc_offset >>- fun (v', rem') -> let reduced_v = if Value.is_included v v' then None else Some v' in @@ -1370,7 +1410,7 @@ module Make match expr.enode with | Lval lval -> second_eval_lval state lval value | _ -> - fst (internal_forward_eval no_fuel state expr) + fst (internal_forward_eval (fast_eval_context state) expr) >>-: fun (v, _, _) -> v in new_value >>- fun evaled -> @@ -1394,8 +1434,9 @@ module Make if (fst report).reduction = Backward then let for_writing = false - and reduction = true in - fst (reduced_lval_to_loc no_fuel ~for_writing ~reduction state lval) + and reduction = true + and context = fast_eval_context state in + fst (reduced_lval_to_loc context ~for_writing ~reduction lval) >>-: fun (loc, _, _, _) -> (* TODO: Loc.narrow *) let record = { record with loc } in @@ -1407,7 +1448,7 @@ module Make else `Value () in evaloc >>- fun () -> - fst (eval_lval no_fuel state lval) >>- fun (record, _, _) -> + fst (eval_lval (fast_eval_context state) lval) >>- fun (record, _, _) -> record.value.v and recursive_descent state expr = @@ -1442,8 +1483,8 @@ module Make module Valuation = Cache - let evaluate ?(valuation=Cache.empty) ?(reduction=true) state expr = - let eval, alarms = subdivided_forward_eval valuation state expr in + let evaluate ?(valuation=Cache.empty) ?(reduction=true) ?subdivnb state expr = + let eval, alarms = subdivided_forward_eval valuation ?subdivnb state expr in let result = if not reduction || Alarmset.is_empty alarms then eval @@ -1455,17 +1496,17 @@ module Make in result, alarms - let copy_lvalue ?(valuation=Cache.empty) state lval = + let copy_lvalue ?(valuation=Cache.empty) ?subdivnb state lval = let expr = Value_util.lval_to_exp lval - and fuel = root_fuel () in + and context = root_context ?subdivnb state in try let record, report = Cache.find' valuation expr in - if less_fuel_than fuel report.fuel + if less_fuel_than context.remaining_fuel report.fuel then `Value (valuation, record.value), record.val_alarms else raise Not_found with Not_found -> cache := valuation; - eval_lval ~indeterminate:true fuel state lval + eval_lval context ~indeterminate:true lval >>=: fun (record, _, volatile) -> let record = reduce_value record in (* Cache the computed result with an appropriate report. *) @@ -1477,29 +1518,30 @@ module Make (* When evaluating an lvalue, we use the subdivided evaluation for the expressions included in the lvalue. *) - let rec evaluate_offsets valuation state = function + let rec evaluate_offsets valuation ?subdivnb state = function | NoOffset -> `Value valuation, Alarmset.none - | Field (_, offset) -> evaluate_offsets valuation state offset + | Field (_, offset) -> evaluate_offsets valuation ?subdivnb state offset | Index (expr, offset) -> - subdivided_forward_eval valuation state expr + subdivided_forward_eval valuation ?subdivnb state expr >>= fun (valuation, _value) -> - evaluate_offsets valuation state offset + evaluate_offsets valuation ?subdivnb state offset - let evaluate_host valuation state = function + let evaluate_host valuation ?subdivnb state = function | Var _ -> `Value valuation, Alarmset.none | Mem expr -> - subdivided_forward_eval valuation state expr >>=: fst + subdivided_forward_eval valuation ?subdivnb state expr >>=: fst - let lvaluate ?(valuation=Cache.empty) ~for_writing state lval = + let lvaluate ?(valuation=Cache.empty) ?subdivnb ~for_writing state lval = (* If [for_writing] is true, the location of [lval] is reduced by removing const bases. Use [for_writing:false] if const bases can be written through a mutable field or an initializing function. *) let for_writing = for_writing && not (Cil.is_mutable_or_initialized lval) in let host, offset = lval in - evaluate_host valuation state host >>= fun valuation -> - evaluate_offsets valuation state offset >>= fun valuation -> + evaluate_host valuation ?subdivnb state host >>= fun valuation -> + evaluate_offsets valuation ?subdivnb state offset >>= fun valuation -> cache := valuation; - lval_to_loc (root_fuel ()) ~for_writing ~reduction:true state lval + let context = root_context ?subdivnb state in + lval_to_loc context ~for_writing ~reduction:true lval >>=. fun (_, typ, _) -> backward_lval (backward_fuel ()) state lval >>-: fun _ -> match Cache.find_loc !cache lval with @@ -1510,7 +1552,11 @@ module Make (* Generate [e == 0] *) let expr = Value_util.normalize_as_cond expr (not positive) in cache := valuation; - root_forward_eval (root_fuel ()) state expr >>=. fun (_v, volatile) -> + (* Currently, no subdivisions are performed during the forward evaluation + in this function, which is used to evaluate the conditions of if(…) + statements in the analysis. *) + let context = root_context ~subdivnb:0 state in + root_forward_eval context expr >>=. fun (_v, volatile) -> (* Reduce by [(e == 0) == 0] *) backward_eval (backward_fuel ()) state expr (Some Value.zero) >>- fun () -> @@ -1518,7 +1564,9 @@ module Make with Not_Exact_Reduction -> (* Avoids reduce_by_cond_enumerate on volatile expressions. *) if volatile then `Value !cache - else Subdivided_Evaluation.reduce_by_enumeration !cache state expr false + else + let context = fast_eval_context state in + Subdivided_Evaluation.reduce_by_enumeration context !cache expr false let assume ?valuation:(valuation=Cache.empty) state expr value = cache := valuation; @@ -1554,14 +1602,14 @@ module Make let expr = Cil.mkBinOp ~loc:expr.eloc Ne expr addr in fst (reduce ~valuation state expr false) - let eval_function_exp funcexp ?args state = + let eval_function_exp ?subdivnb funcexp ?args state = match funcexp.enode with | Lval (Var vinfo, NoOffset) -> `Value [Globals.Functions.get vinfo, Valuation.empty], Alarmset.none | Lval (Mem v, NoOffset) -> begin - evaluate state v >>= fun (valuation, value) -> + evaluate ?subdivnb state v >>= fun (valuation, value) -> let kfs, alarm = Value.resolve_functions value in match kfs with | `Top -> top_function_pointer funcexp diff --git a/src/plugins/value/engine/evaluation.mli b/src/plugins/value/engine/evaluation.mli index b112178c2ac9cb2e021359f162731a2af1b9c2a3..258657a0c581ec61cb2b63c1ad4bd7b93e91a682 100644 --- a/src/plugins/value/engine/evaluation.mli +++ b/src/plugins/value/engine/evaluation.mli @@ -47,12 +47,16 @@ module type S = sig for the expression [expr], and [valuation] contains all the intermediate results of the evaluation. - The [valuation] argument is a cache of already computed expressions. - It is empty by default. - The [reduction] argument allows deactivating the backward reduction - performed after the forward evaluation. *) + Optional arguments are: + - [valuation] is a cache of already computed expressions; empty by default. + - [reduction] allows the deactivation of the backward reduction performed + after the forward evaluation; true by default. + - [subdivnb] is the maximum number of subdivisions performed on non-linear + sub-expressions of [expr]. If a lvalue occurs several times in [expr], + its value can be split up to [subdivnb] times to gain more precision. + Set to the value of the option -eva-subdivide-non-linear by default. *) val evaluate : - ?valuation:Valuation.t -> ?reduction:bool -> + ?valuation:Valuation.t -> ?reduction:bool -> ?subdivnb:int -> state -> exp -> (Valuation.t * value) evaluated (** Computes the value of a lvalue, with possible indeterminateness: the @@ -60,9 +64,11 @@ module type S = sig Also returns the alarms resulting of the evaluation of the lvalue location, and a valuation containing all the intermediate results of the evaluation. The [valuation] argument is a cache of already computed expressions. - It is empty by default. *) + It is empty by default. + [subdivnb] is the maximum number of subdivisions performed on non-linear + expressions. *) val copy_lvalue : - ?valuation:Valuation.t -> + ?valuation:Valuation.t -> ?subdivnb:int -> state -> lval -> (Valuation.t * value flagged_value) evaluated (** [lvaluate ~valuation ~for_writing state lval] evaluates the left value @@ -70,9 +76,11 @@ module type S = sig but evaluates the lvalue into a location and its type. The boolean [for_writing] indicates whether the lvalue is evaluated to be read or written. It is useful for the emission of the alarms, and for the - reduction of the location. *) + reduction of the location. + [subdivnb] is the maximum number of subdivisions performed on non-linear + expressions (including the possible pointer and offset of the lvalue). *) val lvaluate : - ?valuation:Valuation.t -> for_writing:bool -> + ?valuation:Valuation.t -> ?subdivnb:int -> for_writing:bool -> state -> lval -> (Valuation.t * loc * typ) evaluated (** [reduce ~valuation state expr positive] evaluates the expression [expr] @@ -96,7 +104,7 @@ module type S = sig state -> exp -> value -> Valuation.t or_bottom val eval_function_exp: - exp -> ?args:exp list -> state -> + ?subdivnb:int -> exp -> ?args:exp list -> state -> (Kernel_function.t * Valuation.t) list evaluated (** Evaluation of the function argument of a [Call] constructor *) diff --git a/src/plugins/value/engine/subdivided_evaluation.ml b/src/plugins/value/engine/subdivided_evaluation.ml index 893c66fae14163fbaeb6028bbd0c5caaabc31150..b832e789d36607e4baca8e462c25750d3d8e6abe 100644 --- a/src/plugins/value/engine/subdivided_evaluation.ml +++ b/src/plugins/value/engine/subdivided_evaluation.ml @@ -361,10 +361,8 @@ end module type Forward_Evaluation = sig type value type valuation - type state - val evaluate: - ?valuation:valuation -> fuel:int -> - state -> exp -> (valuation * value) evaluated + type context + val evaluate: context -> valuation -> exp -> (valuation * value) evaluated end module Make @@ -646,7 +644,7 @@ module Make [valuation] is the result of the evaluation of [expr] without subdivision. This function returns the alarms and the valuation resulting from the subdivided evaluation. *) - let subdivide_lvals ~fuel subdivnb valuation state expr subexpr lvals = + let subdivide_lvals context valuation subdivnb expr subexpr lvals = let Hypotheses.L variables = Hypotheses.from_list lvals in (* Split function for the subvalues of [lvals]. *) let split = make_split valuation variables in @@ -661,7 +659,7 @@ module Make (* Updates [variables] with their new [subvalues]. *) let valuation = update_variables cleared_valuation variables subvalues in (* Evaluates [expr] with this new valuation. *) - let eval, alarms = Eva.evaluate ~fuel ~valuation state expr in + let eval, alarms = Eva.evaluate context valuation expr in let result = eval >>-: snd in (* Optimization if [subexpr] = [expr]. *) if eq_equal_subexpr @@ -704,12 +702,12 @@ module Make eval_result, alarms (* Builds the information for an lvalue. *) - let get_info ~fuel valuation state lval = + let get_info context valuation lval = let lv_expr = Value_util.lval_to_exp lval in (* Reevaluates the lvalue in the initial state, as its value could have been reduced in the evaluation of the complete expression, and we cannot omit the alarms for the removed values. *) - fst (Eva.evaluate ~fuel ~valuation state lv_expr) >>- fun (valuation, _) -> + fst (Eva.evaluate context valuation lv_expr) >>- fun (valuation, _) -> let lv_record = find_val valuation lv_expr in lv_record.value.v >>-: fun lv_value -> { lval; lv_expr; lv_record; lv_value } @@ -717,8 +715,8 @@ module Make (* Makes a list of lvalue information from a list of lvalues. Removes lvalues whose cvalue is singleton or contains addresses, as we cannot subdivide on such values. *) - let make_info_list ~fuel valuation state lvals = - let get_info = get_info ~fuel valuation state in + let make_info_list context valuation lvals = + let get_info = get_info context valuation in let get_info acc lval = Bottom.add_to_list (get_info lval) acc in let list = List.fold_left get_info [] lvals in List.filter (fun info -> can_be_subdivided (get_cval info.lv_value)) list @@ -732,9 +730,9 @@ module Make | `Value (valuation, result) -> f valuation result alarms (* Subdivided evaluation of [expr] in state [state]. *) - let subdivide_evaluation ~fuel subdivnb initial_valuation state expr = + let subdivide_evaluation context initial_valuation subdivnb expr = (* Evaluation of [expr] without subdivision. *) - let default = Eva.evaluate ~fuel ~valuation:initial_valuation state expr in + let default = Eva.evaluate context initial_valuation expr in default >>> fun valuation result alarms -> (* Do not try to subdivide if the result is singleton or contains some pointers: the better_bound heuristic only works on numerical values. *) @@ -750,7 +748,7 @@ module Make | (subexpr, lvals) :: tail -> (* Retrieve necessary information about the lvalues. Also remove lvalues with pointer or singleton values. *) - let lvals_info = make_info_list ~fuel initial_valuation state lvals in + let lvals_info = make_info_list context initial_valuation lvals in match lvals_info with | [] -> subdivide_subexpr tail valuation result alarms | _ -> @@ -769,16 +767,15 @@ module Make Value_parameters.result ~current:true ~once:true ~dkey "subdividing on %a" (Pretty_utils.pp_list ~sep:", " Printer.pp_lval) lvals; - subdivide_lvals ~fuel subdivnb valuation state expr subexpr lvals_info + subdivide_lvals context valuation subdivnb expr subexpr lvals_info >>> subdivide_subexpr tail in subdivide_subexpr vars valuation result alarms - let evaluate ?(valuation=Valuation.empty) ~fuel state expr = - let subdivnb = Value_parameters.LinearLevel.get () in + let evaluate context valuation ~subdivnb expr = if subdivnb = 0 || not activated - then Eva.evaluate ~valuation ~fuel state expr - else subdivide_evaluation ~fuel subdivnb valuation state expr + then Eva.evaluate context valuation expr + else subdivide_evaluation context valuation subdivnb expr (* ---------------------- Reduction by enumeration ------------------------ *) @@ -867,13 +864,13 @@ module Make let get_influential_exprs valuation expr = get_influential_vars valuation expr [] - let reduce_by_cond_enumerate valuation state cond positive influentials = + let reduce_by_cond_enumerate context valuation cond positive influentials = (* Test whether the condition [expr] may still be true when the sub-expression [e] has the value [v]. *) let condition_may_still_be_true valuation expr record value = let value = { record.value with v = `Value value } in let valuation = Valuation.add valuation expr { record with value } in - let eval, _alarms = Eva.evaluate ~valuation ~fuel:0 state cond in + let eval, _alarms = Eva.evaluate context valuation cond in match eval with | `Bottom -> false | `Value (_valuation, value) -> @@ -917,11 +914,11 @@ module Make (* If the value module contains no cvalue component, this function is inoperative. Otherwise, it calls reduce_by_cond_enumerate with the value accessor for the cvalue component. *) - let reduce_by_enumeration valuation state expr positive = + let reduce_by_enumeration context valuation expr positive = if activated && Value_parameters.EnumerateCond.get () then get_influential_exprs valuation expr >>- fun split_on -> - reduce_by_cond_enumerate valuation state expr positive split_on + reduce_by_cond_enumerate context valuation expr positive split_on else `Value valuation end diff --git a/src/plugins/value/engine/subdivided_evaluation.mli b/src/plugins/value/engine/subdivided_evaluation.mli index 71d582bb70d8a4e1f6bda7d6d77b88051d08f994..25ed9703977c7d4aceb7effc7b63fc5df026d93b 100644 --- a/src/plugins/value/engine/subdivided_evaluation.mli +++ b/src/plugins/value/engine/subdivided_evaluation.mli @@ -25,31 +25,30 @@ by disjunction on their abstract value, in order to gain precision. *) open Cil_types +open Eval module type Forward_Evaluation = sig type value type valuation - type state - val evaluate: - ?valuation:valuation -> fuel:int -> - state -> exp -> (valuation * value) Eval.evaluated + type context + val evaluate: context -> valuation -> exp -> (valuation * value) evaluated end module Make (Value : Abstract.Value.External) (Loc: Abstract_location.S with type value = Value.t) - (Valuation: Eval.Valuation with type value = Value.t - and type loc = Loc.location) + (Valuation: Valuation with type value = Value.t + and type loc = Loc.location) (Eva: Forward_Evaluation with type value := Value.t and type valuation := Valuation.t) : sig val evaluate: - ?valuation:Valuation.t -> fuel:int -> - Eva.state -> exp -> (Valuation.t * Value.t) Eval.evaluated + Eva.context -> Valuation.t -> subdivnb:int -> + exp -> (Valuation.t * Value.t) evaluated val reduce_by_enumeration: - Valuation.t -> Eva.state -> exp -> bool -> Valuation.t Eval.or_bottom + Eva.context -> Valuation.t -> exp -> bool -> Valuation.t or_bottom end diff --git a/src/plugins/value/engine/transfer_stmt.ml b/src/plugins/value/engine/transfer_stmt.ml index fbceb4deb4c00243c9bd0e24800039c304dbd377..b0ffc5fa32a31a07382e3db6f26eb6203d52ea85 100644 --- a/src/plugins/value/engine/transfer_stmt.ml +++ b/src/plugins/value/engine/transfer_stmt.ml @@ -90,6 +90,12 @@ let is_determinate kf = || (name >= "Frama_C" && name < "Frama_D") || Builtins.is_builtin_overridden kf) +let subdivide_stmt = Value_util.get_subdivision + +let subdivide_kinstr = function + | Kglobal -> Value_parameters.LinearLevel.get () + | Kstmt stmt -> subdivide_stmt stmt + (* Used to disambiguate files for Frama_C_dump_each_file directives. *) module DumpFileCounters = State_builder.Hashtbl (Datatype.String.Hashtbl) (Datatype.Int) @@ -150,18 +156,18 @@ module Make (Abstract: Abstractions.Eva) = struct (* The three functions below call evaluation functions and notify the user if they lead to bottom without alarms. *) - let evaluate_and_check ?valuation state expr = - let res = Eval.evaluate ?valuation state expr in + let evaluate_and_check ?valuation ~subdivnb state expr = + let res = Eval.evaluate ?valuation ~subdivnb state expr in report_unreachability state res "the expression %a" Printer.pp_exp expr; res - let lvaluate_and_check ~for_writing ?valuation state lval = - let res = Eval.lvaluate ~for_writing ?valuation state lval in + let lvaluate_and_check ?valuation ~subdivnb ~for_writing state lval = + let res = Eval.lvaluate ?valuation ~subdivnb ~for_writing state lval in report_unreachability state res "the lvalue %a" Printer.pp_lval lval; res - let copy_lvalue_and_check ?valuation state lval = - let res = Eval.copy_lvalue ?valuation state lval in + let copy_lvalue_and_check ?valuation ~subdivnb state lval = + let res = Eval.copy_lvalue ?valuation ~subdivnb state lval in report_unreachability state res "the copy of %a" Printer.pp_lval lval; res @@ -170,17 +176,18 @@ module Make (Abstract: Abstractions.Eva) = struct (* ------------------------------------------------------------------------ *) (* Default assignment: evaluates the right expression. *) - let assign_by_eval state valuation expr = - evaluate_and_check ~valuation state expr >>=: fun (valuation, value) -> + let assign_by_eval ~subdivnb state valuation expr = + evaluate_and_check ~valuation ~subdivnb state expr + >>=: fun (valuation, value) -> Assign value, valuation (* Assignment by copying the value of a right lvalue. *) - let assign_by_copy state valuation lval lloc ltyp = + let assign_by_copy ~subdivnb state valuation lval lloc ltyp = (* This code about garbled mix is specific to the Cvalue domain. Unfortunately, the current API for abstract_domain does not permit distinguishing between an evaluation or a copy. *) Locations.Location_Bytes.do_track_garbled_mix false; - let r = copy_lvalue_and_check ~valuation state lval in + let r = copy_lvalue_and_check ~valuation ~subdivnb state lval in Locations.Location_Bytes.do_track_garbled_mix true; r >>=: fun (valuation, value) -> Copy ({lval; lloc; ltyp}, value), valuation @@ -223,8 +230,9 @@ module Make (Abstract: Abstractions.Eva) = struct (* Assignment. *) let assign_lv_or_ret ~is_ret state kinstr lval expr = let for_writing = for_writing kinstr in - let eval, alarms_loc = lvaluate_and_check ~for_writing state lval in - Alarmset.emit kinstr alarms_loc; + let subdivnb = subdivide_kinstr kinstr in + let eval, alarms = lvaluate_and_check ~for_writing ~subdivnb state lval in + Alarmset.emit kinstr alarms; match eval with | `Bottom -> Kernel.warning ~current:true ~once:true @@ -241,17 +249,20 @@ module Make (Abstract: Abstractions.Eva) = struct else None in let eval, alarms = match lval_copy with - | None -> assert (not is_ret); assign_by_eval state valuation expr + | None -> + assert (not is_ret); + assign_by_eval ~subdivnb state valuation expr | Some right_lval -> + let for_writing = false in (* In case of a copy, checks that the left and right locations are compatible and that they do not overlap. *) - lvaluate_and_check ~for_writing:false ~valuation state right_lval - >>= fun (valuation, right_loc, right_typ) -> - check_overlap ltyp (lval, lloc) (right_lval, right_loc) - >>= fun (lloc, right_loc) -> - if are_compatible lloc right_loc - then assign_by_copy state valuation right_lval right_loc right_typ - else assign_by_eval state valuation expr + lvaluate_and_check ~for_writing ~subdivnb ~valuation state right_lval + >>= fun (valuation, rloc, rtyp) -> + check_overlap ltyp (lval, lloc) (right_lval, rloc) + >>= fun (lloc, rloc) -> + if are_compatible lloc rloc + then assign_by_copy ~subdivnb state valuation right_lval rloc rtyp + else assign_by_eval ~subdivnb state valuation expr in if is_ret then assert (Alarmset.is_empty alarms); Alarmset.emit kinstr alarms; @@ -387,7 +398,7 @@ module Make (Abstract: Abstractions.Eva) = struct If the call has copied the argument, it may be uninitialized. Thus, we also avoid the backward propagation if the formal is uninitialized here. This should not happen in the Assign case above. *) - fst (Eval.copy_lvalue ~valuation:empty state lval) + fst (Eval.copy_lvalue ~valuation:empty ~subdivnb:0 state lval) >>- fun (_valuation, post_value) -> if Bottom.is_included Value.is_included pre_value post_value.v @@ -491,10 +502,10 @@ module Make (Abstract: Abstractions.Eva) = struct evaluates the call argument [expr] in the state [state] and the valuation [valuation]. Returns the value assigned, and the updated valuation. TODO: share more code with [assign]. *) - let evaluate_actual ~determinate valuation state expr = + let evaluate_actual ~subdivnb ~determinate valuation state expr = match expr.enode with | Lval lv -> - lvaluate_and_check ~for_writing:false ~valuation state lv + lvaluate_and_check ~for_writing:false ~subdivnb ~valuation state lv >>= fun (valuation, loc, typ) -> if Int_Base.is_top (Location.size loc) then @@ -502,18 +513,17 @@ module Make (Abstract: Abstractions.Eva) = struct "Function argument %a has unknown size. Aborting" Printer.pp_exp expr; if determinate && Cil.isArithmeticOrPointerType (Cil.typeOfLval lv) - then assign_by_eval state valuation expr - else assign_by_copy state valuation lv loc typ - | _ -> - assign_by_eval state valuation expr + then assign_by_eval ~subdivnb state valuation expr + else assign_by_copy ~subdivnb state valuation lv loc typ + | _ -> assign_by_eval ~subdivnb state valuation expr (* Evaluates the list of the actual arguments of a call. Returns the list of each argument expression associated to its assigned value, and the valuation resulting of the evaluations. *) - let compute_actuals determinate valuation state arguments = + let compute_actuals ~subdivnb ~determinate valuation state arguments = let process expr acc = acc >>= fun (args, valuation) -> - evaluate_actual ~determinate valuation state expr >>=: + evaluate_actual ~subdivnb ~determinate valuation state expr >>=: fun (assigned, valuation) -> (expr, assigned) :: args, valuation in @@ -548,10 +558,10 @@ module Make (Abstract: Abstractions.Eva) = struct in {kf; arguments; rest; return; recursive} - let make_call kf arguments valuation state = + let make_call ~subdivnb kf arguments valuation state = (* Evaluate the arguments of the call. *) let determinate = is_determinate kf in - compute_actuals determinate valuation state arguments + compute_actuals ~subdivnb ~determinate valuation state arguments >>=: fun (args, valuation) -> let call = create_call kf args in call, valuation @@ -589,10 +599,10 @@ module Make (Abstract: Abstractions.Eva) = struct else fun _ _ _ _ -> () (* Frama_C_domain_show_each functions. *) - let domain_show_each name arguments state = + let domain_show_each ~subdivnb name arguments state = let pretty fmt expr = let pp fmt = - match fst (Eval.evaluate state expr) with + match fst (Eval.evaluate ~subdivnb state expr) with | `Bottom -> Format.fprintf fmt "%s" (Unicode.bottom_string ()) | `Value (valuation, _value) -> show_expr valuation state fmt expr in @@ -607,15 +617,15 @@ module Make (Abstract: Abstractions.Eva) = struct let show_offsm = match Domain.get_cvalue, Location.get Main_locations.PLoc.key with | None, _ | _, None -> - fun fmt _ _ -> Format.fprintf fmt "%s" (Unicode.top_string ()) + fun fmt _ _ _ -> Format.fprintf fmt "%s" (Unicode.top_string ()) | Some get_cvalue, Some get_ploc -> - fun fmt expr state -> + fun fmt subdivnb expr state -> match expr.enode with | Lval lval -> begin try let offsm = - fst (Eval.lvaluate ~for_writing:false state lval) + fst (Eval.lvaluate ~for_writing:false ~subdivnb state lval) >>- fun (_, loc, _) -> Eval_op.offsetmap_of_loc (get_ploc loc) (get_cvalue state) in @@ -629,28 +639,30 @@ module Make (Abstract: Abstractions.Eva) = struct (* For scalar expressions, prints the cvalue component of their values. *) let show_value = match Value.get Main_values.CVal.key with - | None -> fun fmt _ _ -> Format.fprintf fmt "%s" (Unicode.top_string ()) + | None -> fun fmt _ _ _ -> Format.fprintf fmt "%s" (Unicode.top_string ()) | Some get_cval -> - fun fmt expr state -> - let value = fst (Eval.evaluate state expr) >>-: snd >>-: get_cval in + fun fmt subdivnb expr state -> + let value = + fst (Eval.evaluate ~subdivnb state expr) >>-: snd >>-: get_cval + in (Bottom.pretty Cvalue.V.pretty) fmt value - let pretty_arguments state arguments = + let pretty_arguments ~subdivnb state arguments = let pretty fmt expr = if Cil.isArithmeticOrPointerType (Cil.typeOf expr) - then show_value fmt expr state - else show_offsm fmt expr state + then show_value fmt subdivnb expr state + else show_offsm fmt subdivnb expr state in Pretty_utils.pp_list ~pre:"@[<hv>" ~sep:",@ " ~suf:"@]" pretty arguments (* Frama_C_show_each functions. *) - let show_each name arguments state = + let show_each ~subdivnb name arguments state = Value_parameters.result ~current:true "@[<hv>%s:@ %a@]%t" - name (pretty_arguments state) arguments Value_util.pp_callstack + name (pretty_arguments ~subdivnb state) arguments Value_util.pp_callstack (* Frama_C_dump_each_file functions. *) - let dump_state_file_exc name arguments state = + let dump_state_file_exc ~subdivnb name arguments state = let size = String.length name in let name = if size > 23 @@ -669,29 +681,30 @@ module Make (Abstract: Abstractions.Eva) = struct Format.fprintf fmt "DUMPING STATE at file %a line %d@." Datatype.Filepath.pretty l.Filepath.pos_path l.Filepath.pos_lnum; + let pretty_args = pretty_arguments ~subdivnb state in if arguments <> [] - then Format.fprintf fmt "Args: %a@." (pretty_arguments state) arguments; + then Format.fprintf fmt "Args: %a@." pretty_args arguments; Format.fprintf fmt "@[<v>%a@]@?" print_state state; close_out ch - let dump_state_file name arguments state = - try dump_state_file_exc name arguments state + let dump_state_file ~subdivnb name arguments state = + try dump_state_file_exc ~subdivnb name arguments state with e -> Value_parameters.warning ~current:true ~once:true "Error during, or invalid call to Frama_C_dump_each_file (%s). Ignoring" (Printexc.to_string e) (** Applies the show_each or dump_each directives. *) - let apply_special_directives kf arguments state = + let apply_special_directives ~subdivnb kf arguments state = let name = Kernel_function.get_name kf in if Ast_info.can_be_cea_function name then if Ast_info.is_cea_function name - then (show_each name arguments state; true) + then (show_each ~subdivnb name arguments state; true) else if Ast_info.is_cea_domain_function name - then (domain_show_each name arguments state; true) + then (domain_show_each ~subdivnb name arguments state; true) else if Ast_info.is_cea_dump_file_function name - then (dump_state_file name arguments state; true) + then (dump_state_file ~subdivnb name arguments state; true) else if Ast_info.is_cea_dump_function name then (dump_state name state; true) else false @@ -719,22 +732,25 @@ module Make (Abstract: Abstractions.Eva) = struct let call stmt lval_option funcexp args state = let ki_call = Kstmt stmt in + let subdivnb = subdivide_stmt stmt in + (* Resolve [funcexp] into the called kernel functions. *) + let functions, alarms = + Eval.eval_function_exp ~subdivnb funcexp ~args state + in + Alarmset.emit ki_call alarms; let cacheable = ref Value_types.Cacheable in let eval = - (* Resolve [funcexp] into the called kernel functions. *) - let functions, alarms = Eval.eval_function_exp funcexp ~args state in - Alarmset.emit ki_call alarms; functions >>- fun functions -> let current_kf = Value_util.current_kf () in let process_one_function kf valuation = (* The special Frama_C_ functions to print states are handled here. *) - if apply_special_directives kf args state + if apply_special_directives ~subdivnb kf args state then let () = apply_cvalue_callback kf ki_call state in `Value ([state]) else (* Create the call. *) - let eval, alarms = make_call kf args valuation state in + let eval, alarms = make_call ~subdivnb kf args valuation state in Alarmset.emit ki_call alarms; eval >>- fun (call, valuation) -> (* Register the call. *) @@ -771,8 +787,11 @@ module Make (Abstract: Abstractions.Eva) = struct | `True | `TrueReduced _ -> Alarmset.none let check_non_overlapping state lvs1 lvs2 = + let lvaluate ~valuation lval = + fst (Eval.lvaluate ~valuation ~for_writing:false ~subdivnb:0 state lval) + in let eval_loc (acc, valuation) lval = - match fst (Eval.lvaluate ~valuation ~for_writing:false state lval) with + match lvaluate ~valuation lval with | `Bottom -> acc, valuation | `Value (valuation, loc, _) -> (lval, loc) :: acc, valuation in diff --git a/src/plugins/value/partitioning/partition.ml b/src/plugins/value/partitioning/partition.ml index eb378dc20bbd738c1af9512c6a722d57450098c7..87940525f00a5e3dc17b9724c26d5883ef0c1078 100644 --- a/src/plugins/value/partitioning/partition.ml +++ b/src/plugins/value/partitioning/partition.ml @@ -229,6 +229,9 @@ struct (* Domains transfer functions. *) module TF = Abstract.Dom.Transfer (Abstract.Eval.Valuation) + (* Evaluation with no reduction and no subdivision. *) + let evaluate = Abstract.Eval.evaluate ~reduction:false ~subdivnb:0 + exception Operation_failed let fail ~exp message = @@ -239,10 +242,10 @@ struct in Pretty_utils.ksfprintf warn_and_raise message - let evaluate_exp_to_ival ?valuation state exp = + let evaluate_exp_to_ival state exp = (* Evaluate the expression *) let valuation, value = - match Abstract.Eval.evaluate ?valuation ~reduction:false state exp with + match evaluate state exp with | `Value (valuation, value), alarms when Alarmset.is_empty alarms -> valuation, value | _ -> @@ -336,7 +339,7 @@ struct let typ = Cil.typeOf expr in let make stamp i = stamp, i, Abstract.Val.inject_int typ i in let expected_values = List.mapi make expected_values in - match fst (Abstract.Eval.evaluate state expr) with + match fst (evaluate state expr) with | `Bottom -> None | `Value (_cache, value) -> let is_included (_, _, v) = Abstract.Val.is_included v value in diff --git a/src/plugins/value/partitioning/partitioning_parameters.ml b/src/plugins/value/partitioning/partitioning_parameters.ml index 33ad321a5cba2cdbbf4c053b7f77fb153a91656b..6b5a0bee6b8a079dd2ac24d98997e3a05d406f1f 100644 --- a/src/plugins/value/partitioning/partitioning_parameters.ml +++ b/src/plugins/value/partitioning/partitioning_parameters.ml @@ -21,7 +21,7 @@ (**************************************************************************) open Value_parameters -open Partitioning_annots +open Eva_annotations open Cil_types let is_return s = match s.skind with Return _ -> true | _ -> false diff --git a/src/plugins/value/partitioning/per_stmt_slevel.ml b/src/plugins/value/partitioning/per_stmt_slevel.ml index ca504df3baeb37c32d6aa5048117373d27e3afb6..d93674f6e63b5429a3a18fb2dd1b76c992117c48 100644 --- a/src/plugins/value/partitioning/per_stmt_slevel.ml +++ b/src/plugins/value/partitioning/per_stmt_slevel.ml @@ -21,7 +21,7 @@ (**************************************************************************) open Cil_types -open Partitioning_annots +open Eva_annotations module G = struct type t = kernel_function diff --git a/src/plugins/value/utils/partitioning_annots.ml b/src/plugins/value/utils/eva_annotations.ml similarity index 91% rename from src/plugins/value/utils/partitioning_annots.ml rename to src/plugins/value/utils/eva_annotations.ml index 804c3c8e15f37979764c7bd0e1194606339a5261..767cf487636ff235b6e01cd46d9b498eb3c9c624 100644 --- a/src/plugins/value/utils/partitioning_annots.ml +++ b/src/plugins/value/utils/eva_annotations.ml @@ -224,3 +224,30 @@ let add_unroll_annot = Unroll.add let add_flow_annot ~emitter ~loc stmt = function | FlowSplit annot -> Split.add ~emitter ~loc stmt annot | FlowMerge annot -> Merge.add ~emitter ~loc stmt annot + + +module Subdivision = Register (struct + type t = int + let name = "subdivide" + let is_loop_annot = false + + let parse ~typing_context:_ = function + | [{lexpr_node = PLconstant (IntConstant i)}] -> + let i = + try int_of_string i + with Failure _ -> raise Parse_error + in + if i < 0 then raise Parse_error; + i + | _ -> raise Parse_error + + let export i = Ext_terms [Logic_const.tinteger i] + let import = function + | Ext_terms [{term_node = TConst (Integer (i, _))}] -> Integer.to_int i + | _ -> assert false + + let print fmt i = Format.pp_print_int fmt i + end) + +let get_subdivision_annot = Subdivision.get +let add_subdivision_annot = Subdivision.add diff --git a/src/plugins/value/utils/partitioning_annots.mli b/src/plugins/value/utils/eva_annotations.mli similarity index 82% rename from src/plugins/value/utils/partitioning_annots.mli rename to src/plugins/value/utils/eva_annotations.mli index a95de0105a702588333f4b03f8d12a7da496211e..9c29a1612aa26943e112d925fe38fce12502be31 100644 --- a/src/plugins/value/utils/partitioning_annots.mli +++ b/src/plugins/value/utils/eva_annotations.mli @@ -20,6 +20,14 @@ (* *) (**************************************************************************) +(** Registers Eva annotations: + - slevel annotations: "slevel default", "slevel merge" and "slevel i" + - loop unroll annotations: "loop unroll term" + - value partitioning annotations: "split term" and "merge term" + - subdivision annotations: "subdivide i" + + Widen hints annotations are still registered in !{widen_hints_ext.ml}. *) + type slevel_annotation = | SlevelMerge | SlevelDefault @@ -34,6 +42,7 @@ type flow_annotation = val get_slevel_annot : Cil_types.stmt -> slevel_annotation option val get_unroll_annot : Cil_types.stmt -> unroll_annotation list val get_flow_annot : Cil_types.stmt -> flow_annotation list +val get_subdivision_annot : Cil_types.stmt -> int list val add_slevel_annot : emitter:Emitter.t -> loc:Cil_types.location -> Cil_types.stmt -> slevel_annotation -> unit @@ -41,3 +50,5 @@ val add_unroll_annot : emitter:Emitter.t -> loc:Cil_types.location -> Cil_types.stmt -> unroll_annotation -> unit val add_flow_annot : emitter:Emitter.t -> loc:Cil_types.location -> Cil_types.stmt -> flow_annotation -> unit +val add_subdivision_annot : emitter:Emitter.t -> loc:Cil_types.location -> + Cil_types.stmt -> int -> unit diff --git a/src/plugins/value/utils/value_util.ml b/src/plugins/value/utils/value_util.ml index 99bfd13838fb8991f534a2720753c88bf5b1a6e7..545c6bfc12471b277f1da3c25e57623db1b473ae 100644 --- a/src/plugins/value/utils/value_util.ml +++ b/src/plugins/value/utils/value_util.ml @@ -71,6 +71,22 @@ let get_slevel kf = try Value_parameters.SlevelFunction.find kf with Not_found -> Value_parameters.SemanticUnrollingLevel.get () +let get_subdivision_option stmt = + try + let kf = Kernel_function.find_englobing_kf stmt in + Value_parameters.LinearLevelFunction.find kf + with Not_found -> Value_parameters.LinearLevel.get () + +let get_subdivision stmt = + match Eva_annotations.get_subdivision_annot stmt with + | [] -> get_subdivision_option stmt + | [x] -> x + | x :: _ -> + Value_parameters.warning ~current:true ~once:true + "Several subdivision annotations at the same statement; selecting %i\ + and ignoring the others." x; + x + let pretty_actuals fmt actuals = let pp fmt (e,x,_) = Cvalue.V.pretty_typ (Some (Cil.typeOf e)) fmt x in Pretty_utils.pp_flowlist pp fmt actuals diff --git a/src/plugins/value/utils/value_util.mli b/src/plugins/value/utils/value_util.mli index a631d797987bfab295360fc4b9e837bcaf042922..1613ca7b695c8eebe6278f633f1495e48795e6dd 100644 --- a/src/plugins/value/utils/value_util.mli +++ b/src/plugins/value/utils/value_util.mli @@ -46,6 +46,7 @@ val pp_callstack : Format.formatter -> unit (* TODO: Document the rest of this file. *) val emitter : Emitter.t val get_slevel : Kernel_function.t -> Value_parameters.SlevelFunction.value +val get_subdivision: stmt -> int val pretty_actuals : Format.formatter -> (Cil_types.exp * Cvalue.V.t * 'b) list -> unit val pretty_current_cfunction_name : Format.formatter -> unit diff --git a/src/plugins/value/value_parameters.ml b/src/plugins/value/value_parameters.ml index 6213ef4f51156fcaffba7d0fd7c1e0a9306a9f4b..a56b71755be5a1106cbe0d50d70965a98076504a 100644 --- a/src/plugins/value/value_parameters.ml +++ b/src/plugins/value/value_parameters.ml @@ -1020,6 +1020,30 @@ module LinearLevel = let () = add_precision_dep LinearLevel.parameter let () = LinearLevel.add_aliases ["-val-subdivide-non-linear"] +let () = Parameter_customize.set_group precision_tuning +module LinearLevelFunction = + Kernel_function_map + (struct + include Datatype.Int + type key = Cil_types.kernel_function + let of_string ~key:_ ~prev:_ s = + Extlib.opt_map + (fun s -> + try int_of_string s + with Failure _ -> + raise (Cannot_build ("'" ^ s ^ "' is not an integer"))) + s + let to_string ~key:_ = Extlib.opt_map string_of_int + end) + (struct + let option_name = "-eva-subdivide-non-linear-function" + let arg_name = "f:n" + let help = "override the global option -eva-subdivide-non-linear with <n>\ + when analyzing the function <f>." + let default = Kernel_function.Map.empty + end) +let () = add_precision_dep LinearLevelFunction.parameter + let () = Parameter_customize.set_group precision_tuning let () = Parameter_customize.argument_may_be_fundecl () module UsePrototype = diff --git a/src/plugins/value/value_parameters.mli b/src/plugins/value/value_parameters.mli index 00db23383f500a22b368d1c34a279ef972cd07ee..f4a069d86ed4859e3deec979674f55ed24463f52 100644 --- a/src/plugins/value/value_parameters.mli +++ b/src/plugins/value/value_parameters.mli @@ -129,6 +129,10 @@ module SkipLibcSpecs: Parameter_sig.Bool module RmAssert: Parameter_sig.Bool module LinearLevel: Parameter_sig.Int +module LinearLevelFunction: + Parameter_sig.Map with type key = Cil_types.kernel_function + and type value = int + module BuiltinsOverrides: Parameter_sig.Map with type key = Cil_types.kernel_function and type value = string diff --git a/tests/value/nonlin.c b/tests/value/nonlin.c index 173929f555a96b0ca01f8e7585879fb4895598af..7395d9b0d4b5940e73762857645d559c76835fe9 100644 --- a/tests/value/nonlin.c +++ b/tests/value/nonlin.c @@ -1,5 +1,5 @@ /* run.config* - STDOPT: +"-eva-subdivide-non-linear 14 -eva-msg-key nonlin" + STDOPT: +"-eva-subdivide-non-linear 14 -eva-subdivide-non-linear-function=local_subdivide:32 -eva-msg-key nonlin" */ #include "__fc_builtin.h" @@ -103,10 +103,25 @@ void subdivide_reduced_value () { int r = t1[i] - t2[i]; } +/* Tests local subdivision via option -eva-subdivide-non-linear-function + and annotations subdivide. */ +void local_subdivide () { + int x = Frama_C_interval(-10, 10); + int y = Frama_C_interval(-10, 10); + /*@ subdivide 0; */ + int imprecise = x*x - 2*x*y + y*y; // No subdivision: [-400..400] + /*@ subdivide 14; */ + int more_precise = x*x - 2*x*y + y*y; // Some subdivisions: [-48..400] + int even_more_precise = x*x - 2*x*y + y*y; // Local subdivision: [-16..400] + /*@ subdivide 100; */ + int precise = x*x - 2*x*y + y*y; // Maximal subdivision: [0..400] +} + void main () { subdivide_integer (); subdivide_pointer (); subdivide_several_variables (); if (v) subdivide_table (); subdivide_reduced_value (); + local_subdivide (); } diff --git a/tests/value/oracle/nonlin.res.oracle b/tests/value/oracle/nonlin.res.oracle index 14387a41a7e3291ac05201ac0be199a89d76142a..ca1b870962dc9a89c3d58e9d29bea23a70cbdfec 100644 --- a/tests/value/oracle/nonlin.res.oracle +++ b/tests/value/oracle/nonlin.res.oracle @@ -24,7 +24,7 @@ [31] ∈ {5} [32..35] ∈ {0} [eva] computing for function subdivide_integer <- main. - Called from tests/value/nonlin.c:107. + Called from tests/value/nonlin.c:121. [eva:nonlin] tests/value/nonlin.c:31: non-linear '((int)z + 675) * ((int)z + 675)', lv 'z' [eva:nonlin] tests/value/nonlin.c:31: subdividing on z @@ -45,7 +45,7 @@ [eva] Recording results for subdivide_integer [eva] Done for function subdivide_integer [eva] computing for function subdivide_pointer <- main. - Called from tests/value/nonlin.c:108. + Called from tests/value/nonlin.c:122. [eva] computing for function Frama_C_interval <- subdivide_pointer <- main. Called from tests/value/nonlin.c:12. [eva] using specification for function Frama_C_interval @@ -68,7 +68,7 @@ [eva] Recording results for subdivide_pointer [eva] Done for function subdivide_pointer [eva] computing for function subdivide_several_variables <- main. - Called from tests/value/nonlin.c:109. + Called from tests/value/nonlin.c:123. [eva] computing for function Frama_C_interval <- subdivide_several_variables <- main. Called from tests/value/nonlin.c:51. @@ -117,7 +117,7 @@ [eva] Recording results for subdivide_several_variables [eva] Done for function subdivide_several_variables [eva] computing for function subdivide_table <- main. - Called from tests/value/nonlin.c:110. + Called from tests/value/nonlin.c:124. [eva] tests/value/nonlin.c:89: loop invariant got status valid. [eva] tests/value/nonlin.c:90: starting to merge loop iterations [eva:nonlin] tests/value/nonlin.c:91: @@ -126,7 +126,7 @@ [eva] Recording results for subdivide_table [eva] Done for function subdivide_table [eva] computing for function subdivide_reduced_value <- main. - Called from tests/value/nonlin.c:111. + Called from tests/value/nonlin.c:125. [eva:nonlin] tests/value/nonlin.c:103: non-linear 't1[i] - t2[i]', lv 'i' [eva:nonlin] tests/value/nonlin.c:103: subdividing on i [eva:alarm] tests/value/nonlin.c:103: Warning: @@ -135,9 +135,36 @@ accessing out of bounds index. assert i < 2; [eva] Recording results for subdivide_reduced_value [eva] Done for function subdivide_reduced_value +[eva] computing for function local_subdivide <- main. + Called from tests/value/nonlin.c:126. +[eva] computing for function Frama_C_interval <- local_subdivide <- main. + Called from tests/value/nonlin.c:109. +[eva] tests/value/nonlin.c:109: + function Frama_C_interval: precondition 'order' got status valid. +[eva] Done for function Frama_C_interval +[eva] computing for function Frama_C_interval <- local_subdivide <- main. + Called from tests/value/nonlin.c:110. +[eva] tests/value/nonlin.c:110: + function Frama_C_interval: precondition 'order' got status valid. +[eva] Done for function Frama_C_interval +[eva:nonlin] tests/value/nonlin.c:114: + non-linear '(x * x - (2 * x) * y) + y * y', lv 'y, x' +[eva:nonlin] tests/value/nonlin.c:114: subdividing on x, y +[eva:nonlin] tests/value/nonlin.c:115: subdividing on x, y +[eva:nonlin] tests/value/nonlin.c:117: subdividing on x, y +[eva] Recording results for local_subdivide +[eva] Done for function local_subdivide [eva] Recording results for main [eva] done for function main [eva] ====== VALUES COMPUTED ====== +[eva:final-states] Values at end of function local_subdivide: + Frama_C_entropy_source ∈ [--..--] + x ∈ [-10..10] + y ∈ [-10..10] + imprecise ∈ [-400..400] + more_precise ∈ [-48..400] + even_more_precise ∈ [-16..400] + precise ∈ [0..400] [eva:final-states] Values at end of function subdivide_integer: z ∈ [-32768..28523] k ∈ [-2..1118367364] @@ -180,11 +207,13 @@ NON TERMINATING FUNCTION [eva:final-states] Values at end of function main: Frama_C_entropy_source ∈ [--..--] +[from] Computing for function local_subdivide +[from] Computing for function Frama_C_interval <-local_subdivide +[from] Done for function Frama_C_interval +[from] Done for function local_subdivide [from] Computing for function subdivide_integer [from] Done for function subdivide_integer [from] Computing for function subdivide_pointer -[from] Computing for function Frama_C_interval <-subdivide_pointer -[from] Done for function Frama_C_interval [from] Done for function subdivide_pointer [from] Computing for function subdivide_reduced_value [from] Done for function subdivide_reduced_value @@ -200,6 +229,8 @@ [from] Function Frama_C_interval: Frama_C_entropy_source FROM Frama_C_entropy_source (and SELF) \result FROM Frama_C_entropy_source; min; max +[from] Function local_subdivide: + Frama_C_entropy_source FROM Frama_C_entropy_source (and SELF) [from] Function subdivide_integer: NO EFFECTS [from] Function subdivide_pointer: @@ -213,6 +244,11 @@ [from] Function main: Frama_C_entropy_source FROM Frama_C_entropy_source (and SELF) [from] ====== END OF DEPENDENCIES ====== +[inout] Out (internal) for function local_subdivide: + Frama_C_entropy_source; x; y; imprecise; more_precise; even_more_precise; + precise +[inout] Inputs for function local_subdivide: + Frama_C_entropy_source [inout] Out (internal) for function subdivide_integer: z; k; l; x; p; i1; i2; r; t[0..100]; idx [inout] Inputs for function subdivide_integer: