diff --git a/nix/mk_tests.nix b/nix/mk_tests.nix index 0a7b37dcc03a4c242e47d0e37968bd74c598eb95..4d35f6a6a678191b21d4f63da3969eb28169a327 100644 --- a/nix/mk_tests.nix +++ b/nix/mk_tests.nix @@ -26,6 +26,7 @@ { lib , alt-ergo +, cvc4 , clang , frama-c , perl @@ -60,7 +61,7 @@ stdenvNoCC.mkDerivation { yq which ] ++ - (if has-wp-proofs then [ alt-ergo ] else []); + (if has-wp-proofs then [ alt-ergo cvc4 ] else []); postPatch = '' patchShebangs . diff --git a/src/libraries/utils/bag.ml b/src/libraries/utils/bag.ml index 59dea90c400108ba97e69fe0d9fa31d23ba3b067..83d9273ce7ad3c7bc48c66355ae58b5bc444e0c2 100644 --- a/src/libraries/utils/bag.ml +++ b/src/libraries/utils/bag.ml @@ -97,6 +97,37 @@ let rec iter f = function | List xs -> List.iter f xs | Concat(a,b) -> iter f a ; iter f b +let find (type a) f xs = + let exception Found of a in + try + iter (fun x -> if f x then raise (Found x)) xs ; + raise Not_found + with Found x -> x + +let find_opt (type a) f xs = + let exception Found of a in + try + iter (fun x -> if f x then raise (Found x)) xs ; + None + with Found x -> Some x + +let find_map (type b) f xs = + let exception Found of b in + try + iter (fun x -> match f x with Some y -> raise (Found y) | None -> ()) xs ; + None + with Found x -> Some x + +let exists f xs = + let exception Found in + try iter (fun x -> if f x then raise Found) xs ; false + with Found -> true + +let for_all f xs = + let exception Found in + try iter (fun x -> if not @@ f x then raise Found) xs ; true + with Found -> false + let rec fold_left f w = function | Empty -> w | Elt x -> f w x diff --git a/src/libraries/utils/bag.mli b/src/libraries/utils/bag.mli index 0dfb4c34ce16c43b4591dcffc2f05c711e42403a..4c9e704b23f688969a974b6fca08e601ef9cc841 100644 --- a/src/libraries/utils/bag.mli +++ b/src/libraries/utils/bag.mli @@ -42,6 +42,17 @@ val iter : ('a -> unit) -> 'a t -> unit val fold_left : ('b -> 'a -> 'b) -> 'b -> 'a t -> 'b val fold_right : ('a -> 'b -> 'b) -> 'a t -> 'b -> 'b +val find : ('a -> bool) -> 'a t -> 'a +val find_opt : ('a -> bool) -> 'a t -> 'a option +val find_map : ('a -> 'b option) -> 'a t -> 'b option +(** [find_map f b]: finds the first element [x] of the bag such + that [f x] returns [Some y], and returns [Some y]. If no such + element exists, returns None. +*) + +val exists : ('a -> bool) -> 'a t -> bool +val for_all : ('a -> bool) -> 'a t -> bool + val filter : ('a -> bool) -> 'a t -> 'a t val partition : ('a -> bool) -> 'a t -> 'a t * 'a t diff --git a/src/plugins/wp/Cache.ml b/src/plugins/wp/Cache.ml index 5e7988978a2a4bd7ef7e975dcd002bf1ba3522ee..3ee82fe8b4aea89b7d1ec7a6d6de393a42d5f709 100644 --- a/src/plugins/wp/Cache.ml +++ b/src/plugins/wp/Cache.ml @@ -156,7 +156,7 @@ let steps_seized steps steplimit = let promote ?timeout ?steplimit (res : VCS.result) = match res.verdict with - | VCS.NoResult | VCS.Computing _ | VCS.Failed -> VCS.no_result + | VCS.NoResult | VCS.Computing _ | VCS.Invalid | VCS.Failed -> VCS.no_result | VCS.Valid | VCS.Unknown -> if not (steps_fits res.prover_steps steplimit) then { res with verdict = Stepout } diff --git a/src/plugins/wp/Changelog b/src/plugins/wp/Changelog index c16ff02fa861910b652cb905e9b218f2da7d25c9..e3b12412e74b8dd0e5e849f4ae71308b5408d84d 100644 --- a/src/plugins/wp/Changelog +++ b/src/plugins/wp/Changelog @@ -24,6 +24,10 @@ Plugin WP <next-release> ############################################################################### +- WP [2024-01-24] Introduce counter examples via the new option + -wp-counter-examples. Also introduce an ACSL extension + to create probes on terms for counter examples. + ############################################################################### Plugin WP 28.0 (Nickel) ############################################################################### diff --git a/src/plugins/wp/Cleaning.ml b/src/plugins/wp/Cleaning.ml index 2672d901653a8a7498bd42ea20f75ec1b077648c..6343457d294e1e33596314f5bfbe4373d0d05730 100644 --- a/src/plugins/wp/Cleaning.ml +++ b/src/plugins/wp/Cleaning.ml @@ -50,7 +50,8 @@ let cup_false = function | FALSE -> FALSE | _ -> TOP -let set_top m p = Vars.fold (fun x m -> Vmap.add x TOP m) (F.varsp p) m +let add_term m t = Vars.fold (fun x m -> Vmap.add x TOP m) (F.vars t) m +let add_prop m p = add_term m (F.e_prop p) let add eq x d m = Vmap.add x (try cup eq (Vmap.find x m) d with Not_found -> EQ d) m let add_true m x = Vmap.add x (try cup_true (Vmap.find x m) with Not_found -> TRUE) m let add_false m x = Vmap.add x (try cup_false (Vmap.find x m) with Not_found -> FALSE) m @@ -64,21 +65,21 @@ let add_fun = add Fun.equal let rec add_pred m p = match F.p_expr p with | And ps -> List.fold_left add_pred m ps - | If(e,a,b) -> add_pred (add_pred (set_top m e) a) b + | If(e,a,b) -> add_pred (add_pred (add_prop m e) a) b | Eq(a,b) -> begin match F.p_expr a , F.p_expr b with | Fvar x , Fvar y -> add_var x y (add_var y x m) - | _ -> set_top m p + | _ -> add_prop m p end | Fvar x -> add_true m x | Not p -> begin match F.p_expr p with | Fvar x -> add_false m x - | _ -> set_top m p + | _ -> add_prop m p end - | _ -> set_top m p + | _ -> add_prop m p let rec add_type m p = match F.p_expr p with @@ -87,9 +88,9 @@ let rec add_type m p = begin match F.e_expr e with | Fvar x -> add_fun x f m - | _ -> set_top m p + | _ -> add_prop m p end - | _ -> set_top m p + | _ -> add_prop m p (* -------------------------------------------------------------------------- *) (* --- Usage --- *) @@ -101,7 +102,8 @@ type usage = { } let create () = { eq_var = Vmap.empty ; eq_fun = Vmap.empty } -let as_atom m p = m.eq_var <- set_top m.eq_var p +let as_term m t = m.eq_var <- add_term m.eq_var t +let as_atom m p = m.eq_var <- add_prop m.eq_var p let as_have m p = m.eq_var <- add_pred m.eq_var p let as_init m p = m.eq_fun <- add_type m.eq_fun p let as_type m p = m.eq_fun <- add_type m.eq_fun p diff --git a/src/plugins/wp/Cleaning.mli b/src/plugins/wp/Cleaning.mli index 091b78fd0269e6626660c9ca0decd2d520c599d0..e849aeb991ebac991c142dbc9ea81a5133af8232 100644 --- a/src/plugins/wp/Cleaning.mli +++ b/src/plugins/wp/Cleaning.mli @@ -29,6 +29,7 @@ open Lang.F type usage val create : unit -> usage +val as_term : usage -> term -> unit val as_atom : usage -> pred -> unit val as_type : usage -> pred -> unit val as_have : usage -> pred -> unit diff --git a/src/plugins/wp/Conditions.ml b/src/plugins/wp/Conditions.ml index 5947885c463bfd83cdf9eb24fc5f770ce7f45cff..bb251b1e1789b5725c01c33d46a3261ee25cc7d3 100644 --- a/src/plugins/wp/Conditions.ml +++ b/src/plugins/wp/Conditions.ml @@ -36,7 +36,7 @@ let dkey_pruning = Wp_parameters.register_category "pruning" (* -------------------------------------------------------------------------- *) type category = - | EMPTY (** Empty Sequence, equivalent to True, but with State. *) + | KEEP (** Has probes, but equivalent to True *) | TRUE (** Logically equivalent to True *) | FALSE (** Logically equivalent to False *) | MAYBE (** Any Hypothesis *) @@ -45,13 +45,12 @@ let c_and c1 c2 = match c1 , c2 with | FALSE , _ | _ , FALSE -> FALSE | MAYBE , _ | _ , MAYBE -> MAYBE - | TRUE , _ | _ , TRUE -> TRUE - | EMPTY , EMPTY -> EMPTY + | TRUE , TRUE -> TRUE + | (KEEP | TRUE) , (KEEP | TRUE) -> KEEP let c_or c1 c2 = match c1 , c2 with | FALSE , FALSE -> FALSE - | EMPTY , EMPTY -> EMPTY | TRUE , TRUE -> TRUE | _ -> MAYBE @@ -61,7 +60,7 @@ let rec cfold_and a f = function let rec cfold_or a f = function | [] -> a | e::es -> cfold_or (c_or a (f e)) f es -let c_conj f es = cfold_and EMPTY f es +let c_conj f es = cfold_and TRUE f es let c_disj f = function [] -> FALSE | e::es -> cfold_or (f e) f es (* -------------------------------------------------------------------------- *) @@ -85,6 +84,7 @@ and sequence = { seq_catg : category ; seq_list : step list ; (* forall i . 0 <= i < n ==> Step_i *) } + and condition = | Type of pred (* related to Type *) | Have of pred @@ -94,6 +94,7 @@ and condition = | Branch of pred * sequence * sequence (* if Pred then Seq_1 else Seq_2 *) | Either of sequence list (* exist i . 0 <= i < n && Sequence_i *) | State of Mstate.state + | Probe of Probe.t * term (* -------------------------------------------------------------------------- *) (* --- Variable Utilities --- *) @@ -107,8 +108,9 @@ let vars_cond = function | Branch(p,sa,sb) -> Vars.union (F.varsp p) (Vars.union sa.seq_vars sb.seq_vars) | Either cases -> vars_seqs cases | State _ -> Vars.empty + | Probe(_,t) -> F.vars t let size_cond = function - | Type _ | When _ | Have _ | Core _ | Init _ | State _ -> 1 + | Type _ | When _ | Have _ | Core _ | Init _ | State _ | Probe _ -> 1 | Branch(_,sa,sb) -> 1 + sa.seq_size + sb.seq_size | Either cases -> List.fold_left (fun n s -> n + s.seq_size) 1 cases let vars_hyp hs = hs.seq_vars @@ -128,7 +130,7 @@ let rec add_core s p = match F.p_expr p with | _ -> if is_core p then Pset.add p s else s let core_cond = function - | Type _ | State _ -> Pset.empty + | Type _ | State _ | Probe _ -> Pset.empty | Have p | When p | Core p | Init p -> add_core Pset.empty p | Branch(_,sa,sb) -> Pset.inter sa.seq_core sb.seq_core | Either [] -> Pset.empty @@ -146,12 +148,13 @@ let core_list s = List.fold_left add_core_step Pset.empty s let catg_seq s = s.seq_catg let catg_cond = function | State _ -> TRUE + | Probe _ -> KEEP | Have p | Type p | When p | Core p | Init p -> begin match F.is_ptrue p with | No -> FALSE | Maybe -> MAYBE - | Yes -> EMPTY + | Yes -> TRUE end | Either cs -> c_disj catg_seq cs | Branch(_,a,b) -> c_or a.seq_catg b.seq_catg @@ -174,8 +177,10 @@ let sequence l = { (* --- Sequence Comparator --- *) (* -------------------------------------------------------------------------- *) +(* Only used for stability after a turn of simpliciations *) let rec equal_cond ca cb = match ca,cb with + | Probe(p,a) , Probe(q,b) -> Probe.equal p q && a == b | State _ , State _ -> true | Type p , Type q | Have p , Have q @@ -187,6 +192,7 @@ let rec equal_cond ca cb = p == q && equal_seq a a' && equal_seq b b' | Either u, Either v -> Qed.Hcons.equal_list equal_seq u v + | Probe _ , _ | _ , Probe _ | State _ , _ | _ , State _ | Type _ , _ | _ , Type _ | Have _ , _ | _ , Have _ @@ -221,7 +227,7 @@ struct | Have p -> Have (fpred core p) | When p -> When (fpred core p) | Init p -> Init (fpred core p) - | (Type _ | Branch _ | Either _ | State _) as cond -> cond + | (Type _ | Branch _ | Either _ | State _ | Probe _) as cond -> cond let fstep core step = let condition = fcond core step.condition in @@ -319,18 +325,18 @@ let pretty = ref (fun _fmt _seq -> ()) let equal (a : sequent) (b : sequent) : bool = F.eqp (snd a) (snd b) && equal_seq (fst a) (fst b) -let is_true = function { seq_catg = TRUE | EMPTY } -> true | _ -> false -let is_empty = function { seq_catg = EMPTY } -> true | _ -> false +let is_true = function { seq_catg = TRUE | KEEP } -> true | _ -> false +let is_empty = function { seq_catg = TRUE } -> true | _ -> false let is_false = function { seq_catg = FALSE } -> true | _ -> false let is_absurd_h h = match h.condition with + | State _ | Probe _ -> false | (Type p | Core p | When p | Have p | Init p) -> p == F.p_false | Branch(_,p,q) -> is_false p && is_false q | Either w -> List.for_all is_false w (* note: an empty w is an absurd hyp *) - | State _ -> false let is_trivial_h h = match h.condition with - | State _ -> false + | State _ | Probe _ -> false | (Type p | Core p | When p | Have p | Init p) -> p == F.p_true | Branch(_,a,b) -> is_true a && is_true b | Either [] -> false @@ -345,7 +351,7 @@ let is_trivial (s:sequent) = is_trivial_hs_p (fst s).seq_list (snd s) (* -------------------------------------------------------------------------- *) let rec pred_cond = function - | State _ -> F.p_true + | State _ | Probe _ -> F.p_true | When p | Type p | Have p | Core p | Init p -> p | Branch(p,a,b) -> F.p_if p (pred_seq a) (pred_seq b) | Either cases -> F.p_any pred_seq cases @@ -394,11 +400,11 @@ let update_cond ?descr ?(deps=[]) ?(warn=Warning.Set.empty) h c = type 'a disjunction = D_TRUE | D_FALSE | D_EITHER of 'a list let disjunction phi es = - let positives = ref false in (* TRUE or EMPTY items *) + let positives = ref false in (* TRUE or KEEP items *) let remains = List.filter (fun e -> match phi e with - | TRUE | EMPTY -> positives := true ; false + | TRUE | KEEP -> positives := true ; false | MAYBE -> true | FALSE -> false ) es in @@ -531,9 +537,11 @@ let intros ps hs = Bundle.add (step ~descr:"Goal" (When p)) hs let state ?descr ?stmt state hs = - let cond = State state in - let s = step ?descr ?stmt cond in - Bundle.add s hs + Bundle.add (step ?descr ?stmt (State state)) hs + +let probe ~loc ?descr ?stmt ~name term hs = + let p = Probe.create ~loc ?stmt ~name () in + Bundle.add (step ?descr ?stmt (Probe(p,term))) hs let assume ?descr ?stmt ?deps ?warn ?(init=false) ?(domain=false) p hs = match F.is_ptrue p with @@ -545,7 +553,7 @@ let assume ?descr ?stmt ?deps ?warn ?(init=false) ?(domain=false) p hs = | Maybe -> begin match Bundle.category hs with - | MAYBE | TRUE | EMPTY -> + | MAYBE | TRUE | KEEP -> let p = exist_intro p in let cond = if init then Init p else if domain then Type p else Have p in @@ -600,10 +608,10 @@ let merge cases = either ~descr:"Merge" cases (* -------------------------------------------------------------------------- *) let rec flat_catg = function - | [] -> EMPTY + | [] -> TRUE | s::cs -> match catg_step s with - | EMPTY -> flat_catg cs + | TRUE -> flat_catg cs | r -> r let flat_cons step tail = @@ -613,13 +621,13 @@ let flat_cons step tail = let flat_concat head tail = match flat_catg head with - | EMPTY -> tail + | TRUE -> tail | FALSE -> head - | MAYBE|TRUE -> + | MAYBE|KEEP -> match flat_catg tail with - | EMPTY -> head + | TRUE -> head | FALSE -> tail - | MAYBE|TRUE -> head @ tail + | MAYBE|KEEP -> head @ tail let core_residual step core = { id = noid ; @@ -635,7 +643,7 @@ let core_residual step core = { let core_branch step p a b = let condition = match a.seq_catg , b.seq_catg with - | (TRUE | EMPTY) , (TRUE|EMPTY) -> Have p_true + | (TRUE | KEEP) , (TRUE|KEEP) -> Have p_true | FALSE , FALSE -> Have p_false | _ -> Branch(p,a,b) in update_cond step condition @@ -644,7 +652,7 @@ let rec flatten_sequence m = function | [] -> [] | step :: seq -> match step.condition with - | State _ -> flat_cons step (flatten_sequence m seq) + | State _ | Probe _ -> flat_cons step (flatten_sequence m seq) | Have p | Type p | When p | Core p | Init p -> begin match F.is_ptrue p with @@ -661,7 +669,7 @@ let rec flatten_sequence m = function let sa = a.seq_list in let sb = b.seq_list in match a.seq_catg , b.seq_catg with - | (TRUE|EMPTY) , (TRUE|EMPTY) -> + | (TRUE|KEEP) , (TRUE|KEEP) -> m := true ; flatten_sequence m seq | _ , FALSE -> m := true ; @@ -698,6 +706,7 @@ let rec flatten_sequence m = function (* -------------------------------------------------------------------------- *) let rec map_condition f = function + | Probe(p,t) -> Probe(p,F.p_lift f t) | State s -> State (Mstate.apply (F.p_lift f) s) | Have p -> Have (f p) | Type p -> Type (f p) @@ -730,6 +739,7 @@ module Ground = Letify.Ground let rec ground_flow ~fwd env h = match h.condition with + | Probe(p,t) -> update_cond h (Probe (p,Ground.e_apply env t)) | State s -> let s = Mstate.apply (Ground.e_apply env) s in update_cond h (State s) @@ -743,8 +753,7 @@ let rec ground_flow ~fwd env h = let b = ground_flowseq ~fwd wb b in update_cond h (Branch(p,a,b)) | Either ws -> - let ws = List.map - (fun w -> ground_flowseq ~fwd (Ground.copy env) w) ws in + let ws = List.map (fun w -> ground_flowseq ~fwd (Ground.copy env) w) ws in update_cond h (Either ws) and ground_flowseq ~fwd env hs = @@ -798,14 +807,14 @@ let dseq_of_step sigma step = let defs = match step.condition with | Init p | Have p | When p | Core p -> Defs.extract (Sigma.p_apply sigma p) - | Type _ | Branch _ | Either _ | State _ -> Defs.empty + | Type _ | Branch _ | Either _ | State _ | Probe _ -> Defs.empty in defs , step let letify_assume sref (_,step) = let current = !sref in begin match step.condition with - | Type _ | Branch _ | Either _ | State _ -> () + | Type _ | Branch _ | Either _ | State _ | Probe _ -> () | Init p | Have p | When p | Core p -> if Wp_parameters.Simpl.get () then sref := Sigma.assume current p @@ -842,6 +851,7 @@ let rec letify_seq sigma0 ~target ~export (seq : step list) = and letify_step dseq dsigma ~required ~target ~used i (d,s) = let sigma = dsigma.(i) in let cond = match s.condition with + | Probe(p,t) -> Probe (p,Sigma.e_apply sigma t) | State s -> State (Mstate.apply (Sigma.e_apply sigma) s) | Init p -> let p = Sigma.p_apply sigma p in @@ -923,6 +933,7 @@ let apply_hyp modified solvers h = | _ -> weaken_and_then_assume p in match h.condition with + | Probe(p,t) -> update_cond h (Probe (p,equivalent_exp solvers t)) | State s -> update_cond h (State (Mstate.apply (equivalent_exp solvers) s)) | Init p -> update_cond h (Init (weaken p)) | Type p -> update_cond h (Type (weaken p)) @@ -982,7 +993,7 @@ let empty = { seq_size = 0 ; seq_vars = Vars.empty ; seq_core = Pset.empty ; - seq_catg = EMPTY ; + seq_catg = TRUE ; seq_list = [] ; } @@ -1016,10 +1027,19 @@ let seq_branch ?stmt p sa sb = (* --- Introduction Utilities --- *) (* -------------------------------------------------------------------------- *) -let lemma g = +let lemma ~loc g = let cc g = let hs,p = forall_intro g in let hs = List.map (fun p -> step (Have p)) hs in + let hs = + if Wp_parameters.CounterExamples.get () then + let freevars = Vars.union (vars_list hs) (F.varsp p) in + List.fold_right + (fun x hs -> + let p = Probe.create ~loc ~name:(Var.basename x) () in + step (Probe(p,e_var x)) :: hs + ) (Vars.elements freevars) hs + else hs in sequence hs , p in Lang.local ~vars:(F.varsp g) cc g @@ -1090,7 +1110,7 @@ struct let collect s = function | Have p | When p | Core p | Init p -> collect_set_def s (F.e_prop p) - | Type _ | Branch _ | Either _ | State _ -> () + | Type _ | Branch _ | Either _ | State _ | Probe _ -> () let subst s = match s.cache with @@ -1104,6 +1124,7 @@ struct let p_apply s p = F.p_subst (subst s) p let rec c_apply s = function + | Probe(p,t) -> Probe (p,e_apply s t) | State m -> State (Mstate.apply (e_apply s) m) | Type p -> Type (p_apply s p) | Init p -> Init (p_apply s p) @@ -1214,7 +1235,7 @@ let rec test_cases (s : hsp) = function | Some _ , Some _ -> test_cases s tail let rec collect_cond m = function - | When _ | Have _ | Type _ | Init _ | Core _ | State _ -> () + | When _ | Have _ | Type _ | Init _ | Core _ | State _ | Probe _ -> () | Branch(p,a,b) -> Letify.Split.add m p ; collect_seq m a ; collect_seq m b | Either cs -> List.iter (collect_seq m) cs @@ -1247,6 +1268,7 @@ let pruning ?(solvers=[]) seq = let rec collect_cond u = function | State _ -> () + | Probe(_,t) -> Cleaning.as_term u t | When p -> Cleaning.as_have u p | Have p -> Cleaning.as_have u p | Core p -> Cleaning.as_have u p @@ -1259,7 +1281,7 @@ and collect_steps u steps = List.iter (fun s -> collect_cond u s.condition) steps let rec clean_cond u = function - | State _ as cond -> cond + | State _ | Probe _ as cond -> cond | When p -> When (Cleaning.filter_pred u p) | Have p -> Have (Cleaning.filter_pred u p) | Core p -> Core (Cleaning.filter_pred u p) @@ -1282,9 +1304,9 @@ and clean_steps u = function let c = clean_cond u s.condition in let seq = clean_steps u seq in match catg_cond c with - | EMPTY -> seq + | TRUE -> seq | FALSE -> [update_cond s c] - | TRUE | MAYBE -> update_cond s c :: seq + | KEEP | MAYBE -> update_cond s c :: seq let clean (s,p) = let u = Cleaning.create () in @@ -1416,6 +1438,12 @@ struct Fset.empty in m.footcalls <- Gmap.add f fs m.footcalls ; fs + let collect_term m t = + begin + m.gs <- FP.union m.gs (gvars_of_term ~deep:true m t) ; + m.xs <- Vars.union m.xs (F.vars t) ; + end + let collect_have m p = begin m.gs <- FP.union m.gs (gvars_of_pred ~deep:true m p) ; @@ -1423,6 +1451,7 @@ struct end let rec collect_condition m = function + | Probe(_,t) -> collect_term m t | Have p | When p | Core p -> collect_have m p | Type _ | Init _ | State _ -> () | Branch(p,sa,sb) -> collect_have m p ; collect_seq m sa ; collect_seq m sb @@ -1449,7 +1478,7 @@ struct | [] -> [] | s :: w -> match s.condition with - | State _ | Have _ | When _ | Core _ | Branch _ | Either _ -> + | State _ | Probe _ | Have _ | When _ | Core _ | Branch _ | Either _ -> s :: filter_steplist m w | Type p -> let p = filter_pred m p in @@ -1602,6 +1631,7 @@ struct let rec collect_step w s = match s.condition with | Type _ | State _ -> w + | Probe(_,t) -> usage w t | Have p | Core p | Init p | When p -> usage w (F.e_prop p) | Branch(p,a,b) -> let wa = collect_seq w a in @@ -1639,7 +1669,7 @@ struct let rec collect_step filter s = match s.condition with | State s -> add_state filter s - | Have _ | Core _ | Init _ | When _ | Type _ -> filter + | Have _ | Core _ | Init _ | When _ | Type _ | Probe _ -> filter | Branch(_p,a,b) -> collect_seq (collect_seq filter a) b | Either ws -> List.fold_left collect_seq filter ws @@ -1699,18 +1729,17 @@ let alter_closure sequent = List.fold_left (fun seq f -> f seq) sequent !closure let hyps s = List.map (fun s -> close_cond s.condition) s.seq_list let head s = match s.condition with - | Have p | When p | Core p | Init p | Type p - | Branch(p,_,_) -> p - | Either _ | State _ -> p_true + | Have p | When p | Core p | Init p | Type p | Branch(p,_,_) -> p + | Either _ | State _ | Probe _ -> p_true let have s = match s.condition with | Have p | When p | Core p | Init p | Type p -> p - | Branch _ | Either _ | State _ -> p_true + | Branch _ | Either _ | State _ | Probe _ -> p_true let condition s = F.p_conj (hyps s) -let close sequent = +let property sequent = let s,goal = alter_closure sequent in - F.p_close (F.p_hyps (hyps s) goal) + (F.p_hyps (hyps s) goal) (* -------------------------------------------------------------------------- *) (* --- Visitor --- *) @@ -1719,6 +1748,21 @@ let close sequent = let list seq = seq.seq_list let iter f seq = List.iter f seq.seq_list +(* -------------------------------------------------------------------------- *) +(* --- Probes --- *) +(* -------------------------------------------------------------------------- *) + +let probes seq = + let pool = ref Probe.Map.empty in + let rec collect_step s = + match s.condition with + | Probe(p,t) -> pool := Probe.Map.add p t !pool + | Branch(_,a,b) -> collect_seq a ; collect_seq b + | Either cs -> List.iter collect_seq cs + | _ -> () + and collect_seq s = List.iter collect_step s.seq_list + in collect_seq seq ; !pool + (* -------------------------------------------------------------------------- *) (* --- Index --- *) (* -------------------------------------------------------------------------- *) @@ -1730,7 +1774,7 @@ let rec index_list k = function and index_step k s = s.id <- k ; let k = succ k in match s.condition with - | Have _ | When _ | Type _ | Core _ | Init _ | State _ -> k + | Have _ | When _ | Type _ | Core _ | Init _ | State _ | Probe _ -> k | Branch(_,a,b) -> index_list (index_list k a.seq_list) b.seq_list | Either cs -> index_case k cs @@ -1754,7 +1798,8 @@ let rec at_list k = function if k < n then at_step (k-1) s.condition else at_list (k - n) w and at_step k = function - | Have _ | When _ | Type _ | Core _ | Init _ | State _ -> assert false + | Have _ | When _ | Type _ | Core _ | Init _ | State _ | Probe _ -> + assert false | Branch(_,a,b) -> let n = a.seq_size in if k < n then @@ -1798,7 +1843,8 @@ let in_sequence_add_list ~replace = else s :: in_list (k-n) h w and in_step k h = function - | Have _ | When _ | Type _ | Core _ | Init _ | State _ -> assert false + | Have _ | When _ | Type _ | Core _ | Init _ | State _ | Probe _ -> + assert false | Branch(p,a,b) -> let n = a.seq_size in if k < n then diff --git a/src/plugins/wp/Conditions.mli b/src/plugins/wp/Conditions.mli index 8194fdef251323169ef6efa15c89e39d777e00bf..4f314f0ac7168d74110139900a53a246f197d994 100644 --- a/src/plugins/wp/Conditions.mli +++ b/src/plugins/wp/Conditions.mli @@ -62,6 +62,7 @@ and condition = | Branch of pred * sequence * sequence (** If-Then-Else *) | Either of sequence list (** Disjunction *) | State of Mstate.state (** Memory Model snapshot *) + | Probe of Probe.t * term (** Named probes *) and sequence (** List of steps *) @@ -143,6 +144,9 @@ val step_at : sequence -> int -> step val is_trivial : sequent -> bool (** Goal is true or hypotheses contains false. *) +val probes : sequence -> term Probe.Map.t +(** Collect all probes in the sequence *) + (** {2 Transformations} *) val map_condition : (pred -> pred) -> condition -> condition @@ -184,7 +188,7 @@ val introduction : sequent -> sequent option val introduction_eq : sequent -> sequent (** Same as [introduction] but returns the same sequent is None *) -val lemma : pred -> sequent +val lemma : loc:location -> pred -> sequent (** Performs existential, universal and hypotheses introductions *) val head : step -> pred @@ -198,8 +202,8 @@ val pred_cond : condition -> pred val condition : sequence -> pred (** With free variables kept. *) -val close : sequent -> pred -(** With free variables {i quantified}. *) +val property : sequent -> pred +(** With free variables kept. *) val at_closure : (sequent -> sequent ) -> unit (** register a transformation applied just before close *) @@ -235,6 +239,10 @@ val merge : bundle list -> bundle Linear complexity is achieved by assuming bundle ordering is consistent over the list. *) +(** Inserts probes to a sequent. *) +val probe : loc:location -> ?descr:string -> ?stmt:stmt -> name:string -> + term -> bundle -> bundle + (** Assumes a list of predicates in a [Type] section on top of the bundle. *) val domain : F.pred list -> bundle -> bundle diff --git a/src/plugins/wp/Filtering.ml b/src/plugins/wp/Filtering.ml index 802135e7cdd30d6fe358f5d87b9f182d1de4aeec..a586f8820eb613fa0eeb946924d308f880c69a5e 100644 --- a/src/plugins/wp/Filtering.ml +++ b/src/plugins/wp/Filtering.ml @@ -272,23 +272,29 @@ struct mutable target : domain ; } + let collect_term env t = + let dp = Value.domain env.usage t in + if not (Domain.separated dp env.target) + then env.target <- Domain.join dp env.target + + let collect_probe env t = + let dp = Value.domain env.usage t in + env.target <- Domain.join dp env.target + let rec collect_hyp env p = match F.p_expr p with | And ps | Or ps -> List.iter (collect_hyp env) ps | Imply(hs,p) -> List.iter (collect_hyp env) hs ; collect_hyp env p - | _ -> - let dp = Value.domain env.usage (F.e_prop p) in - if not (Domain.separated dp env.target) - then - ( env.target <- Domain.join dp env.target ) + | _ -> collect_term env (F.e_prop p) let rec collect_seq env s = Conditions.iter (collect_step env) s and collect_step env s = let open Conditions in match s.condition with | Type _ | State _ -> () + | Probe(_,t) -> collect_probe env t | Core p | Have p | When p | Init p -> collect_hyp env p | Either cs -> List.iter (collect_seq env) cs | Branch(p,a,b) -> diff --git a/src/plugins/wp/LogicCompiler.ml b/src/plugins/wp/LogicCompiler.ml index 8d066e1cca6f7e1aedb0058f9b54562d167612ae..399a5abea2e8384b3fda22a6358e30a2e1405b1a 100644 --- a/src/plugins/wp/LogicCompiler.ml +++ b/src/plugins/wp/LogicCompiler.ml @@ -808,7 +808,7 @@ struct let define_lemma c l = if l.lem_labels <> [] && Wp_parameters.has_dkey dkey_lemma then - Wp_parameters.warning ~source:l.lem_position + Wp_parameters.warning ~source:(fst l.lem_loc) "Lemma '%s' has labels, consider using global invariant instead." l.lem_name ; let { tp_kind = kind ; tp_statement = p } = l.lem_predicate in diff --git a/src/plugins/wp/LogicUsage.ml b/src/plugins/wp/LogicUsage.ml index 6293ff03d718e6bfe78f51c7e0eeb8fb4aafcaac..800f68faa67d635fa88e182fec42cf387307e25c 100644 --- a/src/plugins/wp/LogicUsage.ml +++ b/src/plugins/wp/LogicUsage.ml @@ -58,8 +58,8 @@ let trim name = (* -------------------------------------------------------------------------- *) type logic_lemma = { + lem_loc : location ; lem_name : string ; - lem_position : Filepath.position ; lem_types : string list ; lem_labels : logic_label list ; lem_predicate : toplevel_predicate ; @@ -193,7 +193,7 @@ let pp_profile fmt l = let ip_lemma l = Property.ip_lemma { il_name = l.lem_name; il_labels = l.lem_labels; - il_args = l.lem_types; il_loc = (l.lem_position, l.lem_position); + il_args = l.lem_types; il_loc = l.lem_loc; il_attrs = l.lem_attrs; il_pred = l.lem_predicate; } @@ -201,8 +201,8 @@ let ip_lemma l = let lemma_of_global ~context = function | Dlemma(name,labels,types,pred,attrs,loc) -> { + lem_loc = loc ; lem_name = name ; - lem_position = fst loc ; lem_types = types ; lem_labels = labels ; lem_predicate = pred ; diff --git a/src/plugins/wp/LogicUsage.mli b/src/plugins/wp/LogicUsage.mli index 51428deaf89b1c8ed968fbac37b27c329127e90a..2650640095fe937c099623de24805889641630b0 100644 --- a/src/plugins/wp/LogicUsage.mli +++ b/src/plugins/wp/LogicUsage.mli @@ -31,8 +31,8 @@ open Clabels val basename : varinfo -> string (** Trims the original name *) type logic_lemma = { + lem_loc : location ; lem_name : string ; - lem_position : Filepath.position ; lem_types : string list ; lem_labels : logic_label list ; lem_predicate : toplevel_predicate ; diff --git a/src/plugins/wp/Pattern.ml b/src/plugins/wp/Pattern.ml index 71ad146b74e37e7db1837e3173ca3a30b659a97b..c0660c931eb529f218cdeb03ab8c3efdefd1d641 100644 --- a/src/plugins/wp/Pattern.ml +++ b/src/plugins/wp/Pattern.ml @@ -498,12 +498,13 @@ let order (s : Conditions.step) : int = | Type _ -> 5 | Either _ -> 6 | State _ -> 7 + | Probe _ -> 8 let priority sa sb = order sa - order sb let push (step : Conditions.step) = match step.condition with - | Have _ | When _ | Core _ | Init _ | Type _ | State _ -> () + | Have _ | When _ | Core _ | Init _ | Type _ | State _ | Probe _ -> () | Branch(_,sa,sb) -> Queue.push sa queue ; Queue.push sb queue | Either cs -> List.iter (fun s -> Queue.push s queue) cs diff --git a/src/plugins/wp/Pcfg.ml b/src/plugins/wp/Pcfg.ml index aeefb7a4664a5ae6750b2978b2a6a5d546b5289f..69d0ca8243784104d50ae8ab820a4ce27d419197 100644 --- a/src/plugins/wp/Pcfg.ml +++ b/src/plugins/wp/Pcfg.ml @@ -144,7 +144,7 @@ and ctrl env prev steps next = match steps with | s :: others -> let open Conditions in match s.condition with - | Type _ | Have _ | When _ | Core _ | Init _ -> + | Type _ | Have _ | When _ | Core _ | Init _ | Probe _ -> (* Sequence of Labels on Hyp *) ctrl env prev others next | Branch(_,cthen,celse) -> @@ -187,7 +187,7 @@ let register seq = | { id ; stmt ; descr ; condition = State m } -> let label = label env ~id ?stmt ?descr m in let r = pool descr in r := label :: !r - | { condition = Type _ | Have _ | When _ | Core _ | Init _ } -> () + | { condition = Type _ | Have _ | When _ | Core _ | Init _ | Probe _ } -> () | { condition = Branch(_,cthen,celse) } -> push cthen ; push celse | { condition = Either cases } -> List.iter push cases ) seq ; diff --git a/src/plugins/wp/Pcond.ml b/src/plugins/wp/Pcond.ml index 26c32cbfdacecbe9f5bdc1abdf3bf6756a649831..471a236cd943c599d6ebf3ee6f2f9381bab1d560 100644 --- a/src/plugins/wp/Pcond.ml +++ b/src/plugins/wp/Pcond.ml @@ -37,7 +37,7 @@ let rec alloc_hyp pool f seq = (fun step -> if not (Vars.subset step.vars (Plang.alloc_domain pool)) then match step.condition with - | State _ -> + | State _ | Probe _ -> Plang.alloc_xs pool f step.vars | Have p | When p | Type p | Init p | Core p -> Plang.alloc_p pool f p @@ -179,8 +179,8 @@ open Conditions let mark_step m step = (* sub-sequences are marked recursively marked later *) match step.condition with - | When p | Type p | Have p | Init p | Core p - | Branch(p,_,_) -> F.mark_p m p + | When p | Type p | Have p | Init p | Core p | Branch(p,_,_) -> F.mark_p m p + | Probe(_,t) -> F.mark_e m t | Either _ | State _ -> () let spaced pp fmt a = Format.pp_print_space fmt () ; pp fmt a @@ -215,28 +215,35 @@ class engine (lang : #Plang.engine) = Format.fprintf fmt "@[<hov 4>%a %a = %a.@]" self#pp_clause "Let" self#pp_name x self#pp_core e - method pp_intro ~step ~clause ?(dot=".") fmt p = - ignore step ; + method pp_intro ~clause ?(dot=".") fmt p = Format.fprintf fmt "@[<hov 4>%a %a%s@]" self#pp_clause clause lang#pp_pred p dot + method pp_probe fmt p t = + begin + Format.fprintf fmt "@[<hov 4>%a %a = %a.@]" + self#pp_clause "Probe" Probe.pretty p lang#pp_term t + end + (* -------------------------------------------------------------------------- *) (* --- Block Printers --- *) (* -------------------------------------------------------------------------- *) - method pp_condition ~step fmt = function + method pp_condition fmt step = + match step.condition with | State _ -> () - | Core p -> self#pp_intro ~step ~clause:"Core:" fmt p - | Type p -> self#pp_intro ~step ~clause:"Type:" fmt p - | Init p -> self#pp_intro ~step ~clause:"Init:" fmt p - | Have p -> self#pp_intro ~step ~clause:"Have:" fmt p - | When p -> self#pp_intro ~step ~clause:"When:" fmt p + | Probe(p,t) -> self#pp_probe fmt p t + | Core p -> self#pp_intro ~clause:"Core:" fmt p + | Type p -> self#pp_intro ~clause:"Type:" fmt p + | Init p -> self#pp_intro ~clause:"Init:" fmt p + | Have p -> self#pp_intro ~clause:"Have:" fmt p + | When p -> self#pp_intro ~clause:"When:" fmt p | Branch(p,sa,sb) -> begin - self#pp_intro ~step ~clause:"If" ~dot:"" fmt p ; - if not (Conditions.is_true sa) + self#pp_intro ~clause:"If" ~dot:"" fmt p ; + if not (Conditions.is_empty sa) then self#sequence ~clause:"Then" fmt sa ; - if not (Conditions.is_true sb) + if not (Conditions.is_empty sb) then self#sequence ~clause:"Else" fmt sb ; end | Either cases -> @@ -253,21 +260,21 @@ class engine (lang : #Plang.engine) = method pp_step fmt step = match step.condition with - | State _ -> - self#pp_condition ~step fmt step.condition + | State _ -> self#pp_condition fmt step | _ -> begin ( match step.descr with None -> () | Some s -> spaced self#pp_comment fmt s ) ; Warning.Set.iter (spaced self#pp_warning fmt) step.warn ; List.iter (spaced self#pp_property fmt) step.deps ; - spaced (self#pp_condition ~step) fmt step.condition ; + spaced self#pp_condition fmt step ; end method private sequence ~clause fmt seq = Format.pp_print_space fmt () ; self#pp_block ~clause fmt seq + method pp_block ~clause fmt seq = - if Conditions.is_true seq then + if Conditions.is_empty seq then Format.fprintf fmt "%a {}" self#pp_clause clause else begin @@ -286,7 +293,7 @@ class engine (lang : #Plang.engine) = Format.fprintf fmt "@[<hv 0>" ; List.iter (append (self#define env) fmt) (F.defs marks) ; lang#set_env env ; - if not (Conditions.is_true hs) then + if not (Conditions.is_empty hs) then begin self#pp_block ~clause:"Assume" fmt hs ; Format.pp_print_newline fmt () ; @@ -337,7 +344,8 @@ class seqengine (lang : #state) = object(self) inherit engine lang as super - method private label step = function + method private label step = + match step.condition with | State _ -> (try Some (lang#label_at ~id:step.id) with Not_found -> None) @@ -349,9 +357,9 @@ class seqengine (lang : #state) = if not (Bag.is_empty upd) then Bag.iter ((spaced (lang#pp_update lbl)) fmt) upd - method! pp_condition ~step fmt cond = - match self#label step cond with - | None -> super#pp_condition ~step fmt cond + method! pp_condition fmt step = + match self#label step with + | None -> super#pp_condition fmt step | Some lbl -> let before = match Pcfg.prev lbl with | [ pre ] when (Pcfg.branching pre) -> diff --git a/src/plugins/wp/Pcond.mli b/src/plugins/wp/Pcond.mli index ce7ef09ff46a0c1f800632ec0a1f350494456e77..ef53b285566e780682d0121e3d9c2fe5710d8b3f 100644 --- a/src/plugins/wp/Pcond.mli +++ b/src/plugins/wp/Pcond.mli @@ -79,8 +79,9 @@ class engine : #Plang.engine -> (** Default: [plang#pp_sort] *) method pp_definition : Format.formatter -> string -> term -> unit - method pp_intro : step:step -> clause:string -> ?dot:string -> pred printer - method pp_condition : step:step -> condition printer + method pp_intro : clause:string -> ?dot:string -> pred printer + method pp_probe : Format.formatter -> Probe.t -> term -> unit + method pp_condition : step printer method pp_block : clause:string -> sequence printer method pp_goal : pred printer diff --git a/src/plugins/wp/Probe.ml b/src/plugins/wp/Probe.ml new file mode 100644 index 0000000000000000000000000000000000000000..9c5aa89d7e394125da5c04857dc7330717a4c5f3 --- /dev/null +++ b/src/plugins/wp/Probe.ml @@ -0,0 +1,107 @@ +(**************************************************************************) +(* *) +(* This file is part of WP plug-in of Frama-C. *) +(* *) +(* Copyright (C) 2007-2023 *) +(* CEA (Commissariat a l'energie atomique et aux energies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +open Cil_types + +(* -------------------------------------------------------------------------- *) +(* --- User-Defined Probes --- *) +(* -------------------------------------------------------------------------- *) + +type probe = { + id : int; + name : string ; + stmt : stmt option ; + loc : location ; +} + +let create = + let id = ref (-1) in + fun ~loc ?stmt ~name () -> + incr id; { id = !id ; loc ; stmt ; name } + +module S = +struct + include Datatype.Undefined + let name = "WP.Conditions.Probe.t" + let reprs = [{ + loc = Cil_datatype.Location.unknown; stmt = None; name = ""; id=1 + }] + type t = probe + let hash x = x.id + let equal x y = Int.equal x.id y.id + let compare x y = Int.compare y.id x.id (* lastly created first (wp) *) + let pretty fmt p = + if Wp_parameters.debug_atleast 1 then + Format.fprintf fmt "%s#%d" p.name p.id + else + Format.fprintf fmt "%s" p.name +end + +include Datatype.Make_with_collections(S) + +(* -------------------------------------------------------------------------- *) +(* --- ACSL Extension --- *) +(* -------------------------------------------------------------------------- *) + +let rec name_of_host = function + | TVar { lv_name = x } -> x + | TResult _ -> "result" (* currently not used, but could be one day *) + | TMem t -> name_of_term t + +and name_of_term (t : term) = + match t.term_name with a::_ -> a | _ -> + match t.term_node with + | TLval (lh,_) | TAddrOf (lh,_) | TStartOf (lh,_) -> name_of_host lh + | _ -> raise Not_found + +let annotations stmt = + let collect _emitter annot acc = + match annot.annot_content with + | Cil_types.AExtended (_, _, { + ext_name="probe" ; + ext_kind=Ext_terms ps ; + }) -> List.rev_append ps acc + | _ -> acc in + let mk_probe t = + try name_of_term t, t + with Not_found -> + Wp_parameters.abort ~source:(fst t.term_loc) + "Missing name for probe, use @probe A: term;" + in List.rev_map mk_probe (Annotations.fold_code_annot collect stmt []) + +let parse_probe : Acsl_extension.extension_typer = + fun context _loc terms -> + (* use default context of the code-annotation (like an assert clause) *) + let parse_term = context.type_term context context.pre_state in + Ext_terms (List.map parse_term terms) + +let registered = ref false +let register () = + if not !registered && Wp_parameters.Probes.get () then + begin + registered := true ; + Acsl_extension.register_code_annot "probe" parse_probe false ; + end + +let () = Cmdline.run_after_configuring_stage register + +(* -------------------------------------------------------------------------- *) diff --git a/src/plugins/wp/Probe.mli b/src/plugins/wp/Probe.mli new file mode 100644 index 0000000000000000000000000000000000000000..068dc0fa24d87d3b1a6f80490bccb2b2092729fc --- /dev/null +++ b/src/plugins/wp/Probe.mli @@ -0,0 +1,37 @@ +(**************************************************************************) +(* *) +(* This file is part of WP plug-in of Frama-C. *) +(* *) +(* Copyright (C) 2007-2023 *) +(* CEA (Commissariat a l'energie atomique et aux energies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +open Cil_types + +type probe = private { + id : int; + name : string ; + stmt : stmt option ; + loc : location ; +} + +val annotations : stmt -> (string * term) list +val create : loc:location -> ?stmt:stmt -> name:string -> unit -> probe + +include Datatype.S_with_collections with type t = probe + +(**************************************************************************) diff --git a/src/plugins/wp/ProofScript.ml b/src/plugins/wp/ProofScript.ml index c8d5ede112bf3b6ba8c6d806b31b952f2d358f9d..da270ebd1d85c68271d689e33e8358c975191e5d 100644 --- a/src/plugins/wp/ProofScript.ml +++ b/src/plugins/wp/ProofScript.ml @@ -49,6 +49,7 @@ let s_kind s = match s.condition with | Type _ -> "type" | Init _ -> "init" | Branch _ -> "branch" + | Probe _ -> "probe" | Either _ -> "either" | State _ -> "state" @@ -337,6 +338,7 @@ let json_of_verdict = function | VCS.Timeout -> `String "timeout" | VCS.Stepout -> `String "stepout" | VCS.Failed -> `String "failed" + | VCS.Invalid -> `String "invalid" let verdict_of_json = function | `String "valid" -> VCS.Valid @@ -344,6 +346,7 @@ let verdict_of_json = function | `String "timeout" -> VCS.Timeout | `String "stepout" -> VCS.Stepout | `String "failed" -> VCS.Failed + | `String "invalid" -> VCS.Invalid | _ -> VCS.NoResult let json_of_result (p : VCS.prover) (r : VCS.result) = diff --git a/src/plugins/wp/ProverWhy3.ml b/src/plugins/wp/ProverWhy3.ml index cde11bcadfec61eb768eeef08cb95aedbf969800..b97a388f6c93f06d8676f31e71270a5c3c1183b2 100644 --- a/src/plugins/wp/ProverWhy3.ml +++ b/src/plugins/wp/ProverWhy3.ml @@ -23,6 +23,7 @@ let dkey = Wp_parameters.register_category "prover" let dkey_pp_task = Wp_parameters.register_category "prover:pp_task" let dkey_api = Wp_parameters.register_category "why3_api" +let dkey_model = Wp_parameters.register_category "why3_model" let option_file = LogicBuiltins.create_option ~sanitizer:(fun ~driver_dir x -> Filename.concat driver_dir x) @@ -348,14 +349,16 @@ let rec of_trigger ~cnv t = end let rec of_term ~cnv expected t : Why3.Term.term = - Wp_parameters.debug ~dkey:dkey_api - "of_term %a %a@." - Lang.F.Tau.pretty expected Lang.F.pp_term t; let sort = try Lang.F.typeof t with Not_found -> why3_failure "@[<hov 2>Untyped term: %a@]" Lang.F.pp_term t in + Wp_parameters.debug ~dkey:dkey_api + "of_term %a:%a (expected %a)@." + Lang.F.pp_term t Lang.F.Tau.pretty sort Lang.F.Tau.pretty expected + ; + let ($) f x = f x in let r = try coerce ~cnv sort expected $ Lang.F.Tmap.find t cnv.subst @@ -634,8 +637,7 @@ and share cnv expected t = let t = List.fold_left (fun t (x,e') -> Why3.Term.t_let_close x e' t ) t lets - in - t + in t and mk_lets cnv l = List.fold_left (fun (cnv,lets) e -> @@ -647,9 +649,9 @@ and mk_lets cnv l = let x = Why3.Ident.id_fresh (Lang.F.basename e) in let x = Why3.Term.create_vsymbol x ty in (* Format.printf "lets %a = %a : %a@." - * Why3.Pretty.print_vsty x - * Why3.Pretty.print_term e' - * Why3.Pretty.print_ty (Why3.Term.t_type e'); *) + Why3.Pretty.print_vsty x + Why3.Pretty.print_term e' + Why3.Pretty.print_ty (Why3.Term.t_type e'); *) let cnv = {cnv with subst = Lang.F.Tmap.add e (Why3.Term.t_var x) cnv.subst } in let lets = (x,e')::lets in cnv,lets @@ -676,11 +678,13 @@ and int_or_real ~cnv ~fint ~lint ~pint ~freal ~lreal ~preal a b = t_app_fold ~f:freal ~l:lreal ~p:preal ~cnv Real [a; b] | _ -> assert false +let rebuild cnv t = + let t, cache = Lang.For_export.rebuild ~cache:cnv.convert_for_export t in + cnv.convert_for_export <- cache; + t + let convert cnv expected t = - (* rewrite terms which normal form inside qed are different from the one of the provers *) - let t, convert_for_export = Lang.For_export.rebuild ~cache:cnv.convert_for_export t in - cnv.convert_for_export <- convert_for_export; - Lang.For_export.in_state (share cnv expected) t + Lang.For_export.in_state (share cnv expected) (rebuild cnv t) let mk_binders cnv l = List.fold_left (fun (cnv,lets) v -> @@ -689,8 +693,9 @@ let mk_binders cnv l = | Some ty -> let x = Why3.Ident.id_fresh (Lang.F.Var.basename v) in let x = Why3.Term.create_vsymbol x ty in - let e = Lang.F.e_var v in - let cnv = {cnv with subst = Lang.F.Tmap.add e (Why3.Term.t_var x) cnv.subst } in + let ex = Lang.F.e_var v in + let tx = Why3.Term.t_var x in + let cnv = { cnv with subst = Lang.F.Tmap.add ex tx cnv.subst } in let lets = x::lets in cnv,lets ) (cnv,[]) (List.rev l) @@ -1068,7 +1073,48 @@ class visitor (ctx:context) c = let goal_id = (Why3.Decl.create_prsymbol (Why3.Ident.id_fresh "wp_goal")) -let prove_goal ~id ~title ~name ?axioms t = +let add_model_trace (probes: Lang.F.term Probe.Map.t) cnv t = + let open Why3 in + if Probe.Map.is_empty probes then t else + let task = Task.add_meta t Driver.meta_get_counterexmp [Theory.MAstr ""] in + let create_id (p:Probe.t) ty = + let attr = Ident.create_model_trace_attr (string_of_int p.id) in + let attrs = Ident.Sattr.singleton attr in + let loc = match p.loc with + ({Filepath.pos_path;pos_lnum=l1;pos_cnum=c1}, + {Filepath.pos_lnum=l2;pos_cnum=c2}) -> + Why3.Loc.user_position + (Filepath.Normalized.to_pretty_string pos_path) l1 c1 l2 c2 + in Term.create_lsymbol (Ident.id_fresh ~loc ~attrs p.name) [] ty + in + let fold (p:Probe.t) (term:Lang.F.term) task = + let term' = share cnv (Lang.F.typeof term) term in + let id = create_id p term'.t_ty in + let task = Task.add_param_decl task id in + let eq_id = Why3.Decl.create_prsymbol (Why3.Ident.id_fresh "ce_eq") in + let eq = Term.t_equ (Term.t_app id [] term'.t_ty) term' in + let decl = Why3.Decl.create_prop_decl Paxiom eq_id eq in + Task.add_decl task decl + in + Probe.Map.fold fold probes task + +let convert_freevariables ~probes ~cnv t = + let freevars = Probe.Map.fold + (fun _ t vars -> Lang.F.Vars.union vars (Lang.F.vars t)) + probes (Lang.F.vars t) in + let cnv,lss = + Lang.F.Vars.fold (fun (v:Lang.F.Var.t) (cnv,lss) -> + let ty = of_tau ~cnv @@ Lang.F.tau_of_var v in + let x = Why3.Ident.id_fresh (Lang.F.Var.basename v) in + let ls = Why3.Term.create_lsymbol x [] ty in + let ex = Lang.F.e_var v in + let tx = Why3.Term.t_app ls [] ty in + let cnv = { cnv with subst = Lang.F.Tmap.add ex tx cnv.subst } in + (cnv,ls::lss)) freevars (cnv,[]) + in + cnv,lss + +let prove_goal ~id ~title ~name ?axioms ?(probes=Probe.Map.empty) t = (* Format.printf "why3_of_qed start@."; *) let goal = Definitions.cluster ~id ~title () in let ctx = empty_context name in @@ -1083,33 +1129,42 @@ let prove_goal ~id ~title ~name ?axioms t = v#add_builtin_lib; v#vgoal axioms t; let cnv = empty_cnv ~polarity:`Positive ctx in - let t = convert cnv Prop (Lang.F.e_prop t) in - let decl = Why3.Decl.create_prop_decl Pgoal goal_id t in - let th = Why3.Theory.close_theory ctx.th in - if Wp_parameters.has_print_generated () then begin - let th_uc_tmp = Why3.Theory.add_decl ~warn:false ctx.th decl in - let th_tmp = Why3.Theory.close_theory th_uc_tmp in - Wp_parameters.debug ~dkey:Wp_parameters.cat_print_generated "%a" - Why3.Pretty.print_theory th_tmp - end; - th, decl - -let prove_prop ?axioms ~pid prop = + let t = rebuild cnv (Lang.F.e_prop t) in + let probes = Probe.Map.map (rebuild cnv) probes in + Lang.For_export.in_state + begin fun () -> + let cnv,lss = convert_freevariables ~probes ~cnv t in + let goal = share cnv Prop t in + let decl = Why3.Decl.create_prop_decl Pgoal goal_id goal in + let th = Why3.Theory.close_theory ctx.th in + if Wp_parameters.has_print_generated () then begin + let th_uc_tmp = Why3.Theory.add_decl ~warn:false ctx.th decl in + let th_tmp = Why3.Theory.close_theory th_uc_tmp in + Wp_parameters.debug ~dkey:Wp_parameters.cat_print_generated "%a" + Why3.Pretty.print_theory th_tmp + end; + let t = None in + let t = Why3.Task.use_export t th in + let t = List.fold_left Why3.Task.add_param_decl t lss in + let t = add_model_trace probes cnv t in + Why3.Task.add_decl t decl + end () + +let prove_prop ?probes ?axioms ~pid prop = let id = WpPropId.get_propid pid in let title = Pretty_utils.to_string WpPropId.pretty pid in let name = "WP" in - let th, decl = prove_goal ?axioms ~id ~title ~name prop in - let t = None in - let t = Why3.Task.use_export t th in - Why3.Task.add_decl t decl + prove_goal ?axioms ?probes ~id ~title ~name prop -let task_of_wpo wpo = +let compute_probes ~ce ~pid goal = + if ce then Wpo.GOAL.compute_probes ~pid goal else Probe.Map.empty + +let task_of_wpo ~ce wpo = let v = wpo.Wpo.po_formula in let pid = wpo.Wpo.po_pid in - let axioms = v.Wpo.VC_Annot.axioms in - let prop = Wpo.GOAL.compute_proof ~pid v.Wpo.VC_Annot.goal in - (* Format.printf "Goal: %a@." Lang.F.pp_pred prop; *) - prove_prop ~pid prop ?axioms + let prop = Wpo.GOAL.compute_proof ~pid ~opened:ce v.goal in + let probes = compute_probes ~ce ~pid v.goal in + prove_prop ~pid ?axioms:v.axioms ~probes prop, probes (* -------------------------------------------------------------------------- *) (* --- Prover Task --- *) @@ -1153,7 +1208,50 @@ type prover_call = { mutable killed : bool ; } -let ping_prover_call ~config p = +let has_model_attr attrs = + Why3.Ident.Sattr.fold_left (fun acc (e:Why3.Ident.attribute) -> + match Extlib.string_del_prefix "model_trace:" e.attr_string with + | None -> acc + | Some _ as a -> a + ) None attrs + +let debug_model (res:Why3.Call_provers.prover_result) = + Wp_parameters.debug ~dkey:dkey_model "%t" + begin fun fmt -> + List.iter + begin fun (res,model) -> + Format.fprintf fmt "@[<hov 2>model %a: %a@]@\n" + Why3.Call_provers.print_prover_answer res + (Why3.Model_parser.print_model_human + ~filter_similar:false + ~print_attrs:true) model + end + res.pr_models + end + +let get_model probes (res:Why3.Call_provers.prover_result) = + if Wp_parameters.has_dkey dkey_model && not @@ Probe.Map.is_empty probes then + debug_model (res:Why3.Call_provers.prover_result); + (* we take the second model because it should be the most precise?? *) + match Why3.Check_ce.select_model_last_non_empty res.pr_models with + | None -> Probe.Map.empty + | Some model -> + let index = Hashtbl.create 0 in + let elements = Why3.Model_parser.get_model_elements model in + List.iter + (fun (e:Why3.Model_parser.model_element) -> + match has_model_attr e.me_attrs with + | None -> () + | Some id -> Hashtbl.add index id e.me_concrete_value) + elements ; + Probe.Map.filter_map + (fun (p:Probe.t) _ -> + let id = string_of_int p.id in + try Some (Hashtbl.find index id) + with Not_found -> None + ) probes + +let ping_prover_call ~config ~probes p = match Why3.Call_provers.query_call p.call with | NoUpdates | ProverStarted -> @@ -1187,7 +1285,13 @@ let ping_prover_call ~config p = | Valid -> VCS.result ~time ~steps:pr.pr_steps VCS.Valid | OutOfMemory -> VCS.failed "out of memory" | StepLimitExceeded -> VCS.result ?steps:p.steps VCS.Stepout - | Unknown _ | Invalid -> VCS.unknown + | Invalid -> + debug_model pr; + VCS.result ~time:pr.pr_time ~steps:pr.pr_steps + ~model:(get_model probes pr) VCS.Invalid + | Unknown _ -> + debug_model pr; + VCS.result ~model:(get_model probes pr) VCS.Unknown | _ when p.interrupted -> VCS.timeout p.timeout | Failure s -> VCS.failed s | HighFailure -> @@ -1205,7 +1309,7 @@ let ping_prover_call ~config p = VCS.pp_result r; Task.Return (Task.Result r) -let call_prover_task ~timeout ~steps ~config prover call = +let call_prover_task ~timeout ~steps ~config ~probes prover call = Wp_parameters.debug ~dkey "Why3 run prover %a with timeout %f, steps %d@." Why3.Whyconf.print_prover prover (Option.value ~default:(0.0) timeout) @@ -1222,7 +1326,7 @@ let call_prover_task ~timeout ~steps ~config prover call = pcall.killed <- true ; Why3.Call_provers.interrupt_call ~config call ; Task.Yield - | Task.Coin -> ping_prover_call ~config pcall + | Task.Coin -> ping_prover_call ~config ~probes pcall in Task.async ping @@ -1230,29 +1334,40 @@ let call_prover_task ~timeout ~steps ~config prover call = (* --- Batch Prover --- *) (* -------------------------------------------------------------------------- *) +let output_task wpo drv ?(script : Filepath.Normalized.t option) prover task = + let file = Wpo.DISK.file_goal + ~pid:wpo.Wpo.po_pid + ~model:wpo.Wpo.po_model + ~prover:(VCS.Why3 prover) in + Command.print_file file + begin fun fmt -> + Format.fprintf fmt "(* WP Task for Prover %s *)@\n" + (Why3Provers.ident_why3 prover) ; + let old = Option.map + (fun fscript -> + let hash = Digest.file fscript |> Digest.to_hex in + Format.fprintf fmt "(* WP Script %s *)@\n" hash ; + open_in fscript + ) (script :> string option) in + let _ = Why3.Driver.print_task_prepared ?old drv fmt task in + Option.iter close_in old ; + end + + let digest_task wpo drv ?(script : Filepath.Normalized.t option) prover task = + output_task wpo drv ?script prover task; let file = Wpo.DISK.file_goal ~pid:wpo.Wpo.po_pid ~model:wpo.Wpo.po_model ~prover:(VCS.Why3 prover) in begin - Command.print_file file - begin fun fmt -> - Format.fprintf fmt "(* WP Task for Prover %s *)@\n" - (Why3Provers.ident_why3 prover) ; - let old = Option.map - (fun fscript -> - let hash = Digest.file fscript |> Digest.to_hex in - Format.fprintf fmt "(* WP Script %s *)@\n" hash ; - open_in fscript - ) (script :> string option) in - let _ = Why3.Driver.print_task_prepared ?old drv fmt task in - Option.iter close_in old ; - end ; Digest.file (file :> string) |> Digest.to_hex end -let run_batch pconf driver ~config ?(script : Filepath.Normalized.t option) ~timeout ~steplimit ~memlimit +let run_batch pconf driver ~config + ?(script : Filepath.Normalized.t option) + ~timeout ~steplimit ~memlimit + ?(probes=Probe.Map.empty) prover task = let steps = match steplimit with Some 0 -> None | _ -> steplimit in let limit = @@ -1277,9 +1392,10 @@ let run_batch pconf driver ~config ?(script : Filepath.Normalized.t option) ~tim let command = Why3.Whyconf.get_complete_command pconf ~with_steps in Wp_parameters.debug ~dkey "Prover command %S" command ; let inplace = if script <> None then Some true else None in - let call = Why3.Driver.prove_task_prepared ?old:(script :> string option) ?inplace + let call = + Why3.Driver.prove_task_prepared ?old:(script :> string option) ?inplace ~command ~limit ~config driver task in - call_prover_task ~config ~timeout ~steps prover call + call_prover_task ~config ~timeout ~steps ~probes prover call (* -------------------------------------------------------------------------- *) (* --- Interactive Prover (Coq) --- *) @@ -1321,14 +1437,14 @@ let editor ~script ~merge ~config pconf driver task = if merge then updatescript ~script driver task ; let command = editor_command pconf in Wp_parameters.debug ~dkey "Editor command %S" command ; - call_prover_task ~config ~timeout:None ~steps:None pconf.prover @@ + let probes = Probe.Map.empty in + call_prover_task ~config ~timeout:None ~steps:None ~probes pconf.prover @@ Why3.Call_provers.call_editor ~command ~config (script :> string) end -let compile ~(script : Filepath.Normalized.t) ~timeout ~memlimit ~config wpo pconf driver prover task = - let digest = digest_task wpo driver ~script in - let runner = run_batch ~config pconf driver ~script ~memlimit in - Cache.get_result ~digest ~runner ~timeout ~steplimit:None prover task +let compile ~script ~timeout ~memlimit ~config pconf driver prover task = + run_batch ~config pconf driver ~script ~timeout ~memlimit ~steplimit:None + ~probes:Probe.Map.empty prover task let prepare ~mode wpo driver task = let ext = Filename.extension (Why3.Driver.file_of_task driver "S" "T" task) in @@ -1345,7 +1461,7 @@ let prepare ~mode wpo driver task = end else None -let interactive ~mode wpo pconf ~config driver prover task = +let interactive ~mode ~config wpo pconf driver prover task = let time = Wp_parameters.InteractiveTimeout.get () in let mem = Wp_parameters.Memlimit.get () in let timeout = if time <= 0 then None else Some (float time) in @@ -1363,30 +1479,42 @@ let interactive ~mode wpo pconf ~config driver prover task = Why3.Whyconf.print_prover prover (script :> string) ; match mode with | VCS.Batch -> - compile ~script ~timeout ~memlimit ~config wpo pconf driver prover task + compile ~script ~timeout ~memlimit ~config pconf driver prover task | VCS.Update -> if merge then updatescript ~script driver task ; - compile ~script ~timeout ~memlimit ~config wpo pconf driver prover task + compile ~script ~timeout ~memlimit ~config pconf driver prover task | VCS.Edit -> let open Task in editor ~script ~merge ~config pconf driver task >>= fun _ -> - compile ~script ~timeout ~memlimit ~config wpo pconf driver prover task + compile ~script ~timeout ~memlimit ~config pconf driver prover task | VCS.Fix -> let open Task in - compile ~script ~timeout ~memlimit ~config wpo pconf driver prover task + compile ~script ~timeout ~memlimit ~config pconf driver prover task >>= fun r -> if VCS.is_valid r then return r else editor ~script ~merge ~config pconf driver task >>= fun _ -> - compile ~script ~timeout ~memlimit ~config wpo pconf driver prover task + compile ~script ~timeout ~memlimit ~config pconf driver prover task | VCS.FixUpdate -> let open Task in if merge then updatescript ~script driver task ; - compile ~script ~timeout ~memlimit ~config wpo pconf driver prover task + compile ~script ~timeout ~memlimit ~config pconf driver prover task >>= fun r -> if VCS.is_valid r then return r else let merge = false in editor ~script ~merge ~config pconf driver task >>= fun _ -> - compile ~script ~timeout ~memlimit ~config wpo pconf driver prover task + compile ~script ~timeout ~memlimit ~config pconf driver prover task + +let automated ~config ~probes ~timeout ~steplimit ~memlimit + wpo pconf drv prover task = + if Wp_parameters.has_out () then output_task wpo drv prover task; + if Probe.Map.is_empty probes then + Cache.get_result + ~digest:(digest_task wpo drv) + ~runner:(run_batch ~config ~probes ~memlimit pconf drv ?script:None) + ~timeout ~steplimit prover task + else + run_batch ~config ~probes ~memlimit ~timeout ~steplimit + pconf drv prover task (* -------------------------------------------------------------------------- *) (* --- Prove WPO --- *) @@ -1418,7 +1546,13 @@ let build_proof_task ?(mode=VCS.Batch) ?timeout ?steplimit ?memlimit try (* Always generate common task *) let context = Wpo.get_context wpo in - let task = WpContext.on_context context task_of_wpo wpo in + let ce,prover = + if Wp_parameters.CounterExamples.get () then + match Why3Provers.with_counter_examples prover with + | Some prover_ce -> true,prover_ce + | None -> false,prover + else false, prover in + let task,probes = WpContext.on_context context (task_of_wpo ~ce) wpo in if Wp_parameters.Generate.get () then Task.return VCS.no_result (* Only generate *) else @@ -1430,12 +1564,10 @@ let build_proof_task ?(mode=VCS.Batch) ?timeout ?steplimit ?memlimit Task.return VCS.valid else if pconf.interactive then - interactive ~mode wpo pconf ~config drv prover task + interactive ~mode ~config wpo pconf drv prover task else - Cache.get_result - ~digest:(digest_task wpo drv) - ~runner:(run_batch ~config pconf ~memlimit drv ?script:None) - ~timeout ~steplimit prover task + automated ~config ~probes ~timeout ~steplimit ~memlimit + wpo pconf drv prover task with exn -> if Wp_parameters.has_dkey dkey_api then Wp_parameters.fatal "[Why3 Error] %a@\n%s" diff --git a/src/plugins/wp/Stats.ml b/src/plugins/wp/Stats.ml index 751717b63be655b0ebcc1e301e51980f0fbf5b71..0dc5e536e08304e28ac273bf06816993ad4cac4c 100644 --- a/src/plugins/wp/Stats.ml +++ b/src/plugins/wp/Stats.ml @@ -45,7 +45,7 @@ type stats = { noresult : int ; failed : int ; cached : int ; - cachemiss : int ; + cacheable : int ; } (* -------------------------------------------------------------------------- *) @@ -110,12 +110,15 @@ let empty = { noresult = 0 ; failed = 0 ; cached = 0 ; - cachemiss = 0 ; + cacheable = 0 ; } -let cacheable p r = p <> Qed && not @@ VCS.is_trivial r +let cacheable p r = + p <> Qed && not @@ VCS.is_trivial r && + VCS.is_auto p && not @@ VCS.has_counter_examples p + +let add_cacheable n (p,r) = if cacheable p r then succ n else n let add_cached n (p,r) = if cacheable p r && r.cached then succ n else n -let add_cachemiss n (p,r) = if cacheable p r && not r.cached then succ n else n let results ~smoke results = let (p,r) = VCS.best results in @@ -131,7 +134,7 @@ let results ~smoke results = best = r.verdict ; tactics = 0 ; proved ; timeout ; unknown ; failed ; noresult ; cached = List.fold_left add_cached 0 results ; - cachemiss = List.fold_left add_cachemiss 0 results ; + cacheable = List.fold_left add_cacheable 0 results ; provers = if p = Qed then [Qed,if smoke then qsmoked r else pqed r] else @@ -151,7 +154,7 @@ let add a b = noresult = a.noresult + b.noresult ; failed = a.failed + b.failed ; cached = a.cached + b.cached ; - cachemiss = a.cachemiss + b.cachemiss ; + cacheable = a.cacheable + b.cacheable ; } let tactical ~qed children = @@ -162,7 +165,7 @@ let tactical ~qed children = List.fold_left add { empty with best ; provers ; tactics = 1 } children let script stats = - let cached = stats.cachemiss = 0 in + let cached = stats.cached = stats.cacheable in let solver = List.fold_left (fun t (p,s) -> if p = Qed then t +. s.time else t) 0.0 stats.provers in let time = List.fold_left @@ -207,13 +210,14 @@ let pp_stats ~shell ~cache fmt s = let updating = Cache.is_updating cache in let qed_only = match s.provers with [Qed,_] -> true | _ -> false in let print_cache = not qed_only && Cache.is_active cache in + let cachemiss = s.cacheable - s.cached in List.iter (fun (p,pr) -> let success = truncate pr.success in let print_proofs = success > 0 && total > 1 in let print_perfo = pr.time > Rformat.epsilon && - (not shell || (not updating && s.cachemiss > 0)) + (not shell || (not updating && cachemiss > 0)) in if p != Qed || qed_only || print_perfo || print_proofs then @@ -227,21 +231,20 @@ let pp_stats ~shell ~cache fmt s = Format.fprintf fmt ")" end ) s.provers ; - if shell && s.cachemiss > 0 && not updating then - Format.fprintf fmt " (Cache miss %d)" s.cachemiss + if shell && cachemiss > 0 && not updating then + Format.fprintf fmt " (Cache miss %d)" cachemiss else if print_cache then - let cacheable = s.cachemiss + s.cached in - if cacheable = 0 then + if s.cacheable = 0 then if s.best = Valid then Format.pp_print_string fmt " (Trivial)" else Format.pp_print_string fmt " (No Cache)" else - if updating || s.cachemiss = 0 then + if updating || cachemiss = 0 then Format.pp_print_string fmt " (Cached)" else - Format.fprintf fmt " (Cached %d/%d)" s.cached cacheable + Format.fprintf fmt " (Cached %d/%d)" s.cached s.cacheable let pretty = pp_stats ~shell:false ~cache:NoCache diff --git a/src/plugins/wp/Stats.mli b/src/plugins/wp/Stats.mli index 5ac456b19aec6300fdf00a9481bf986c2b5ca95a..ea1cd5dc478d992cba41d0702ae7bc1a41a7a63a 100644 --- a/src/plugins/wp/Stats.mli +++ b/src/plugins/wp/Stats.mli @@ -47,7 +47,7 @@ type stats = { noresult : int ; (** number of no-result sub-goals *) failed : int ; (** number of failed sub-goals *) cached : int ; (** number of cached prover results *) - cachemiss : int ; (** number of non-cached prover results *) + cacheable : int ; (** number of prover results that can be cached *) } val pp_pstats : Format.formatter -> pstats -> unit diff --git a/src/plugins/wp/Strategy.ml b/src/plugins/wp/Strategy.ml index ce46be2cb48423c1cadd404da761628d9d1f2291..acdfb3c00a77dc3c03d637d06accf4fc7b6405d4 100644 --- a/src/plugins/wp/Strategy.ml +++ b/src/plugins/wp/Strategy.ml @@ -55,6 +55,8 @@ let occurs_q p q = occurs_e (F.e_prop p) (F.e_prop q) let lookup_step env queue s = match s.condition with | State _ -> Empty + | Probe(_,t) -> + if t == env.target then Inside(Step s,env.target) else Empty | When p | Have p | Init p | Core p | Type p -> let p = Lang.F.e_prop p in if p == env.target then Clause(Step s) else diff --git a/src/plugins/wp/TacChoice.ml b/src/plugins/wp/TacChoice.ml index 27d558c7afbb2b0b75a12c6deec0a8b06c26c022..b760331b12d9dcbbeb1e4b55d517e9c06c744897 100644 --- a/src/plugins/wp/TacChoice.ml +++ b/src/plugins/wp/TacChoice.ml @@ -78,7 +78,7 @@ class absurd = let seq = Conditions.replace ~at:s.id emp seq in [ "Absurd" , (fst seq , F.p_not p) ] in Applicable absurd - | Branch _ | Either _ | State _ -> + | Branch _ | Either _ | State _ | Probe _ -> Not_applicable end end @@ -106,7 +106,7 @@ class contrapose = let hs = Conditions.replace ~at:s.id goal (hs , F.p_false) in [ "Contrapose" , (fst hs , F.p_not p) ] in Applicable contrapose - | Branch _ | Either _ | State _ -> + | Branch _ | Either _ | State _ | Probe _ -> Not_applicable end diff --git a/src/plugins/wp/TacInduction.ml b/src/plugins/wp/TacInduction.ml index 41d5f7d56ec20026d6c59dadf2e775ced00cc3dd..1403da9adbac755c49caa03aabbbbf8f6dd87e0a 100644 --- a/src/plugins/wp/TacInduction.ml +++ b/src/plugins/wp/TacInduction.ml @@ -33,6 +33,7 @@ let filter_step n hs s = match s.Conditions.condition with | (Have _ | Type _ | Core _ | Init _ | When _) -> Conditions.map_step (filter_pred n hs) s + | Probe _ -> s | (State _ | Branch _ | Either _) as c -> if F.Vars.mem n s.vars then (hs := Conditions.pred_cond c :: !hs ; Conditions.step (Have F.p_true)) diff --git a/src/plugins/wp/TacInstance.ml b/src/plugins/wp/TacInstance.ml index edbaea2f364b4c23fc2de92e76ad8103c52ffb5e..a73274c60faa70efd4decfa5192bb901bd958797 100644 --- a/src/plugins/wp/TacInstance.ml +++ b/src/plugins/wp/TacInstance.ml @@ -126,8 +126,7 @@ let bind ~side bindings property : Tactical.process = match side with | None -> instance_goal ~title:"Witness" bindings property - | Some s -> - let open Conditions in + | Some (s : Conditions.step) -> instance_have ?title:s.descr ~at:s.id bindings property let filter tau e = diff --git a/src/plugins/wp/TacSplit.ml b/src/plugins/wp/TacSplit.ml index 6c13ec59f5f7b137615277817ae71043c8dc8a2b..0e1e31f1e1e64650d38e9585f6be8390aa834e96 100644 --- a/src/plugins/wp/TacSplit.ml +++ b/src/plugins/wp/TacSplit.ml @@ -301,7 +301,7 @@ class split = | Clause(Step step) -> begin match step.condition with - | State _ -> Not_applicable + | State _ | Probe _ -> Not_applicable | Branch(p,_,_) -> feedback#set_title "Split (branch)" ; feedback#set_descr "Decompose Conditional into Branches" ; diff --git a/src/plugins/wp/VCS.ml b/src/plugins/wp/VCS.ml index 857ba148917782a60ae40d9de246383c23a6e411..1fef10ad99975088ac10a8bc0a17ff13f3108b1b 100644 --- a/src/plugins/wp/VCS.ml +++ b/src/plugins/wp/VCS.ml @@ -131,6 +131,10 @@ let is_auto = function not prover_config.interactive with Not_found -> true +let has_counter_examples = function + | Qed | Tactical -> false + | Why3 p -> Why3Provers.with_counter_examples p <> None + let cmp_prover p q = match p,q with | Qed , Qed -> 0 @@ -209,8 +213,11 @@ type verdict = | Stepout | Computing of (unit -> unit) (* kill function *) | Valid + | Invalid (* model *) | Failed +type model = Why3Provers.model Probe.Map.t + type result = { verdict : verdict ; cached : bool ; @@ -219,10 +226,11 @@ type result = { prover_steps : int ; prover_errpos : Lexing.position option ; prover_errmsg : string ; + prover_model : model ; } let is_result = function - | Valid | Unknown | Timeout | Stepout | Failed -> true + | Valid | Invalid | Unknown | Timeout | Stepout | Failed -> true | NoResult | Computing _ -> false let is_verdict r = is_result r.verdict @@ -233,7 +241,7 @@ let is_computing = function { verdict=Computing _ } -> true | _ -> false let is_proved ~smoke = function | NoResult | Computing _ | Failed -> false | Valid -> not smoke - | Unknown | Timeout | Stepout -> smoke + | Unknown | Timeout | Stepout | Invalid -> smoke let configure r = let valid = (r.verdict = Valid) in @@ -277,7 +285,7 @@ let autofit r = time_fits r.prover_time && step_fits r.prover_steps -let result ?(cached=false) ?(solver=0.0) ?(time=0.0) ?(steps=0) verdict = +let result ?(model=Probe.Map.empty) ?(cached=false) ?(solver=0.0) ?(time=0.0) ?(steps=0) verdict = { verdict ; cached = cached ; @@ -286,6 +294,7 @@ let result ?(cached=false) ?(solver=0.0) ?(time=0.0) ?(steps=0) verdict = prover_steps = steps ; prover_errpos = None ; prover_errmsg = "" ; + prover_model = model; } let no_result = result NoResult @@ -302,6 +311,7 @@ let failed ?pos msg = { prover_steps = 0 ; prover_errpos = pos ; prover_errmsg = msg ; + prover_model = Probe.Map.empty ; } let cached r = if is_verdict r then { r with cached=true } else r @@ -330,18 +340,24 @@ let pp_perf_shell fmt r = let name_of_verdict = function | NoResult | Computing _ -> "none" | Valid -> "valid" + | Invalid -> "invalid" | Failed -> "failed" | Unknown -> "unknown" | Stepout -> "stepout" | Timeout -> "timeout" +let pp_hasmodel fmt (r : result) = + if not @@ Probe.Map.is_empty r.prover_model then + Format.fprintf fmt " (Model)" + let pp_result fmt r = match r.verdict with | NoResult -> Format.pp_print_string fmt "No Result" | Computing _ -> Format.pp_print_string fmt "Computing" | Failed -> Format.fprintf fmt "Failed@ %s" r.prover_errmsg | Valid -> Format.fprintf fmt "Valid%a" pp_perf_shell r - | Unknown -> Format.fprintf fmt "Unknown%a" pp_perf_shell r + | Invalid -> Format.fprintf fmt "Invalid%a" pp_hasmodel r + | Unknown -> Format.fprintf fmt "Unknown%a%a" pp_hasmodel r pp_perf_shell r | Stepout -> Format.fprintf fmt "Step limit%a" pp_perf_shell r | Timeout -> Format.fprintf fmt "Timeout%a" pp_perf_shell r @@ -357,8 +373,10 @@ let pp_cache_miss fmt st updating prover result = then Format.fprintf fmt "%s%a (missing cache)" st pp_perf_forced result else - Format.pp_print_string fmt @@ - if is_valid result then "Valid" else "Unsuccess" + if is_valid result then + Format.pp_print_string fmt "Valid" + else + Format.fprintf fmt "Unsuccess%a" pp_hasmodel result let pp_result_qualif ?(updating=true) prover result fmt = if Wp_parameters.has_dkey dkey_shell then @@ -367,13 +385,20 @@ let pp_result_qualif ?(updating=true) prover result fmt = | Computing _ -> Format.pp_print_string fmt "Computing" | Failed -> Format.fprintf fmt "Failed@ %s" result.prover_errmsg | Valid -> pp_cache_miss fmt "Valid" updating prover result + | Invalid -> pp_cache_miss fmt "Invalid" updating prover result | Unknown -> pp_cache_miss fmt "Unsuccess" updating prover result | Timeout -> pp_cache_miss fmt "Timeout" updating prover result | Stepout -> pp_cache_miss fmt "Stepout" updating prover result else pp_result fmt result -(* highest is best *) +let pp_model fmt model = + Probe.Map.iter + (fun probe model -> + Format.fprintf fmt "@[<hov 2>Model %a = %a@]@\n" + Probe.pretty probe Why3Provers.pp_model model + ) model + let vrank = function | Computing _ -> 0 | NoResult -> 1 @@ -382,6 +407,7 @@ let vrank = function | Timeout -> 4 | Stepout -> 5 | Valid -> 6 + | Invalid -> 7 let conjunction a b = match a,b with @@ -390,14 +416,22 @@ let conjunction a b = | r , Valid -> r | _ -> if vrank b > vrank a then b else a +let msize m = Probe.Map.fold (fun _ _ n -> succ n) m 0 + let compare p q = - let r = vrank q.verdict - vrank p.verdict in - if r <> 0 then r else - let s = Stdlib.compare p.prover_steps q.prover_steps in + match is_valid p , is_valid q with + | true , false -> (-1) + | false , true -> (+1) + | _ -> + let s = msize q.prover_model - msize p.prover_model in if s <> 0 then s else - let t = Stdlib.compare p.prover_time q.prover_time in - if t <> 0 then t else - Stdlib.compare p.solver_time q.solver_time + let r = vrank q.verdict - vrank p.verdict in + if r <> 0 then r else + let s = Stdlib.compare p.prover_steps q.prover_steps in + if s <> 0 then s else + let t = Stdlib.compare p.prover_time q.prover_time in + if t <> 0 then t else + Stdlib.compare p.solver_time q.solver_time let bestp pr1 pr2 = if compare (snd pr1) (snd pr2) <= 0 then pr1 else pr2 let best = List.fold_left bestp (Qed,no_result) diff --git a/src/plugins/wp/VCS.mli b/src/plugins/wp/VCS.mli index 4cdd7100839840271af0468fe3315d004a44a0aa..62a7f6b5d91a1546eccaaaba409ee49884e0f22e 100644 --- a/src/plugins/wp/VCS.mli +++ b/src/plugins/wp/VCS.mli @@ -92,8 +92,11 @@ type verdict = | Stepout | Computing of (unit -> unit) (* kill function *) | Valid + | Invalid (* model *) | Failed +type model = Why3Provers.model Probe.Map.t + type result = { verdict : verdict ; cached : bool ; @@ -102,6 +105,7 @@ type result = { prover_steps : int ; prover_errpos : Lexing.position option ; prover_errmsg : string ; + prover_model : model ; } val no_result : result @@ -114,9 +118,11 @@ val failed : ?pos:Lexing.position -> string -> result val kfailed : ?pos:Lexing.position -> ('a,Format.formatter,unit,result) format4 -> 'a val cached : result -> result (** only for true verdicts *) -val result : ?cached:bool -> ?solver:float -> ?time:float -> ?steps:int -> verdict -> result +val result : ?model:model -> ?cached:bool -> + ?solver:float -> ?time:float -> ?steps:int -> verdict -> result val is_auto : prover -> bool +val has_counter_examples : prover -> bool val is_prover : prover -> bool val is_result : verdict -> bool val is_verdict : result -> bool @@ -132,6 +138,7 @@ val autofit : result -> bool (** Result that fits the default configuration *) val name_of_verdict : verdict -> string val pp_result : Format.formatter -> result -> unit +val pp_model : Format.formatter -> model -> unit val pp_result_qualif : ?updating:bool -> prover -> result -> Format.formatter -> unit diff --git a/src/plugins/wp/Why3Provers.ml b/src/plugins/wp/Why3Provers.ml index 38a9e97129761c0fae3e4dae9a1904f9c1e6b3be..2a958392965192ef88c06af011782957f59be145 100644 --- a/src/plugins/wp/Why3Provers.ml +++ b/src/plugins/wp/Why3Provers.ml @@ -103,6 +103,9 @@ let title ?(version=true) p = else p.Why3.Whyconf.prover_name let compare = Why3.Whyconf.Prover.compare let is_mainstream p = p.Why3.Whyconf.prover_altern = "" +let has_counter_examples p = + List.mem "counterexamples" @@ + String.split_on_char '+' p.Why3.Whyconf.prover_altern let provers () = Why3.Whyconf.Mprover.keys (Why3.Whyconf.get_provers (config ())) @@ -119,4 +122,18 @@ let has_shortcut p s = | None -> false | Some p' -> Why3.Whyconf.Prover.equal p p' +let with_counter_examples p = + if has_counter_examples p then Some p else + let name = p.prover_name in + List.find_opt + (fun (q : t) -> q.prover_name = name && has_counter_examples q) + @@ provers () + +(* -------------------------------------------------------------------------- *) +(* --- Models --- *) +(* -------------------------------------------------------------------------- *) + +type model = Why3.Model_parser.concrete_syntax_term +let pp_model = Why3.Model_parser.print_concrete_term + (* -------------------------------------------------------------------------- *) diff --git a/src/plugins/wp/Why3Provers.mli b/src/plugins/wp/Why3Provers.mli index 1db0e9baca5fff878fabe171193c101217736217..7992e2b140e31a5bcb2875896f5be6fca71d9924 100644 --- a/src/plugins/wp/Why3Provers.mli +++ b/src/plugins/wp/Why3Provers.mli @@ -41,6 +41,12 @@ val provers : unit -> t list val provers_set : unit -> Why3.Whyconf.Sprover.t val is_available : t -> bool val is_mainstream : t -> bool +val has_counter_examples : t -> bool +val with_counter_examples : t -> t option val has_shortcut : t -> string -> bool + +type model = Why3.Model_parser.concrete_syntax_term +val pp_model : model Pretty_utils.formatter + (**************************************************************************) diff --git a/src/plugins/wp/cfgCalculus.ml b/src/plugins/wp/cfgCalculus.ml index 38bbdb98b0a8099861d3ae4cba5f747d5e0b67b7..ab494fb2497ada67372a7d7cccfd7ae5bfd734f5 100644 --- a/src/plugins/wp/cfgCalculus.ml +++ b/src/plugins/wp/cfgCalculus.ml @@ -226,8 +226,12 @@ struct opt_fold (prove_property env) code_verified @@ opt_fold (use_property env) code_admitted w in + let probes = Probe.annotations s in + let do_probe env (probe,term) = + W.add_probe env.we ~stmt:s probe term in let pi = W.label env.we (Some s) (Clabels.stmt s) @@ + List.fold_right (do_probe env) probes @@ List.fold_right (do_assert env) cas @@ control env a s in @@ -466,6 +470,13 @@ struct I.process_global_init env.we env.mode.kf @@ W.scope env.we [] SC_Global w + let do_probe_formal env x w = + let probes = Wp_parameters.CounterExamples.get () in + if probes then + let term = Cil_builder.Exp.(cil_term ~loc:x.vdecl @@ var x) in + W.add_probe env.we x.vname term w + else w + let do_preconditions env ~formals (b : CfgAnnot.behavior) w = let kf = env.mode.kf in let init = CfgInfos.is_entry_point kf in @@ -482,6 +493,7 @@ struct List.fold_right (use_property env) b.bhv_requires @@ List.fold_right (prove_property env) b.bhv_smokes @@ List.fold_right (use_property env) side_behaviors @@ + List.fold_right (do_probe_formal env) formals @@ (* frame-in *) W.scope env.we formals SC_Frame_in w diff --git a/src/plugins/wp/cfgDump.ml b/src/plugins/wp/cfgDump.ml index f62ff22ea6d025ac733334260a9ba83bde191888..d33831400fab4b910dc8a954fb52ac8735e571d6 100644 --- a/src/plugins/wp/cfgDump.ml +++ b/src/plugins/wp/cfgDump.ml @@ -89,6 +89,16 @@ let new_env ?lvars kf : t_env = ignore lvars ; kf let add_axiom _p _l = () +let add_probe _env ?stmt p t k = + ignore stmt ; + let u = node () in + if Wp_parameters.debug_atleast 1 then + Format.fprintf !out " %a [ label=\"Probe %a\" ] ;@." pretty u Printer.pp_term t + else + Format.fprintf !out " %a [ label=\"Probe %s\" ] ;@." pretty u p ; + Format.fprintf !out " %a -> %a [ style=dotted ] ;@." pretty u pretty k ; + link u k ; u + let add_hyp ?for_pid _env (pid,pred) k = ignore(for_pid); let u = node () in diff --git a/src/plugins/wp/cfgWP.ml b/src/plugins/wp/cfgWP.ml index c43def5003d3af4a6f85c1bbf2e1ddd8fc5455a6..337e1a2fb00afffcc41a8e133c1da20c85d7c7e6 100644 --- a/src/plugins/wp/cfgWP.ml +++ b/src/plugins/wp/cfgWP.ml @@ -303,6 +303,19 @@ struct path = path ; } + let probe_vc ~loc ?descr ?stmt ?warn ~name term vc = + let vars = F.vars term in + let hyps = Conditions.probe ~loc ?descr ?stmt ~name term vc.hyps in + let wrns = match warn with + | None -> vc.warn + | Some w -> Warning.Set.union w vc.warn in + { hyps = hyps ; + vars = vars ; + warn = wrns ; + goal = vc.goal ; + deps = vc.deps ; + path = vc.path } + (* -------------------------------------------------------------------------- *) (* --- Branching --- *) (* -------------------------------------------------------------------------- *) @@ -366,12 +379,7 @@ struct let deps = List.fold_left (fun d vc -> D.union d vc.deps) D.empty vcs in let warn = List.fold_left (fun d vc -> W.union d vc.warn) W.empty vcs in let path = List.fold_left (fun d vc -> S.union d vc.path) S.empty vcs in - { hyps = hyps ; - goal = goal ; - vars = vars ; - deps = deps ; - warn = warn ; - path = path } + { hyps ; goal ; vars ; deps ; warn ; path } (* -------------------------------------------------------------------------- *) (* --- Merging and Branching with Splitters --- *) @@ -527,6 +535,19 @@ struct let add_axiom _id _l = () + let add_probe wenv ?stmt probe term wp = in_wenv wenv wp + (fun env wp -> + let outcome = + Warning.catch + ~severe:false ~effect:"Skip probe" + (L.term env) term in + match outcome with + | Warning.Failed _warn -> wp + | Warning.Result(warn,value) -> + let add_probe_vc = + probe_vc ~loc:term.term_loc ?stmt ~warn ~name:probe value in + { wp with vcs = gmap add_probe_vc wp.vcs }) + let add_hyp ?for_pid wenv (hpid,predicate) wp = in_wenv wenv wp (fun env wp -> let outcome = Warning.catch @@ -1439,9 +1460,9 @@ struct (* -------------------------------------------------------------------------- *) let wp_scope env wp ~descr scope xs = - let post = L.current env in - let pre = M.alloc post xs in - let hs = M.scope { pre ; post } scope xs in + let sigma = L.current env in + let pre = M.alloc sigma xs in + let hs = M.scope { pre ; post = sigma } scope xs in let vcs = gmap (assume_vc ~descr hs) wp.vcs in { wp with sigma = Some pre ; vcs = vcs } @@ -1631,7 +1652,7 @@ struct let id = WpPropId.mk_lemma_id l in let def = L.lemma l in let model = WpContext.get_model () in - let sequent = Conditions.lemma def.l_lemma in + let sequent = Conditions.lemma ~loc:l.lem_loc def.l_lemma in let vca = { Wpo.VC_Annot.axioms = Some (def.l_cluster, l.lem_depends) ; goal = GOAL.make sequent ; diff --git a/src/plugins/wp/doc/manual/wp_plugin.tex b/src/plugins/wp/doc/manual/wp_plugin.tex index 1fe93a479d2e507698e3169d3b90919429915f43..8c6f6ae198dd42769f4294a21206d70ea4c03b79 100644 --- a/src/plugins/wp/doc/manual/wp_plugin.tex +++ b/src/plugins/wp/doc/manual/wp_plugin.tex @@ -1227,7 +1227,7 @@ would never be provable. The \textsf{WP} plug-in can generate smoke-tests to detect such inconsistencies. Basically, it consists in checking if \verb+\false+ is provable under the requirements -or assumptions of a behaviour, or under the invariants of a loop. Also, a simple reachability +or assumptions of a behavior, or under the invariants of a loop. Also, a simple reachability analysis is performed to track dead-code statements and calls that neither terminates nor exits. This is best-effort verification : if at least one prover succeed in proving \verb+\false+, @@ -1494,6 +1494,146 @@ obligations are: yet. Its content is not cleaned up automatically. \end{description} +\subsection{Probes and Counter Examples} + +\emph{Introduced since Frama-C 29.0 (Copper)} + +Depending on prover capabilities, \textsf{WP} can trigger counter example +generation for non successful goals. Provers supporting counter example +generation can be identified by the \verb$"counterexamples"$ variant after their +name. For example, in the prover selection example above, \verb+cvc4-ce+ and +\verb+z3-ce+ are such provers. + +To enable counter examples generation by \textsf{WP} you shall use +\verb+-wp-counter-examples+ option. Then, for each selected solver that has a +counter-examples variant, this variant will be used instead and counter-examples +will be asked to the solver in case of unknown result. Notice that you can also +use counter-examples solvers \emph{without} asking for counter-examples. For +instance, with option \verb+-wp-prover cvc4-ce+, \textsf{WP} will use this +version of the prover, even if counter examples is not active; the prover will +not be asked to report models, unless counter examples \emph{is} +activate. Notice that solvers variants with counter-examples support are +generally a bit slower than their original version. + +A proof obligation for which a counter-example has been generated is reported by +\textsf{WP} by the string \verb+"(Model)"+ after its proof status. For instance: + +\begin{shell} +# ./bin/frama-c -wp ~/work/wrong.i -wp-prover cvc4 -wp-counter-examples +[...] +[wp] 6 goals scheduled +[wp] [Unknown] typed_g_ensures (Qed 4ms) (CVC4) (No Cache) (Model) +[wp] [Cache] not used +[wp] Proved goals: 6 / 7 + Unreachable: 1 + Qed: 5 (0.72ms) + Unknown: 1 +\end{shell} + +Notice that cache usage is disabled for provers asked to generate a +counter-example, since models are currently not stored in \textsf{WP}'s +cache\footnote{This feature might be added in future versions of the plugin.}. + +A counter-example consists in a \emph{Model}, that is, a map from +\emph{Variables} or \emph{Probes} (see below) to \emph{Values}, for which the +proof obligation formula is expected to be \verb+False+. More precisely, the +Model reports on \emph{necessary} conditions for the proof obligation to be +wrong, although they might not be \emph{sufficient} in practice. + +Generated models are reported by \textsf{WP} along with prover results when +either \verb+-wp-print+ or \verb+-wp-status+ options are used. The interactive +proof transformer (\textsf{TIP}) also displays models in the graphical user +interface, when available. + +Probes are generalization of variables. They are named expressions for which the +prover is asked to output a value in the model. \textsf{WP} will automatically +introduce probes for formal parameters of functions and universally quantified +variables of lemmas. Developers can introduce their own probes by using the +\verb+@probe+ annotation. For instance: + +\begin{lstlisting}[style=c] + int a[4][5]; + /*@ + ensures \false; + assigns \nothing; + */ + int value(int i, int j) { + //@ probe Offset: &a[i][j] - &a[0][0]; + return a[i][j]; + } +} +\end{lstlisting} + +Probes \emph{must} be named \textsf{ACSL} terms. As a short cut, for very +simple probes such as variables, the name can be omitted. Several probes might +have the same name, although it is not really convenient for debugging. The +general syntax for the annotation is: +\[ +\mathtt{probe} \; a_1: t_1, \ldots, a_n: t_n \;\mathtt{;} +\] + +Probes are normalized by \textsf{Qed} during simplification of proof obligations +by \textsf{WP}. Their (simplified) term definitions are reported when printing +proof obligations, and their model values are reported when printing proof +results, when available. For instance: + +\begin{shell} + # frama-c [...] -wp-status + [...] + Goal Post-condition (file /Users/correnson/work/matrix.i, line 3) in 'value': + Assume { + Type: is_sint32(i) /\ is_sint32(j). + Probe i = i. + Probe j = j. + Probe Offset = ((4 * j) + (20 * i)) / 4. + } + Prove: false. + Prover CVC4 1.8 returns Unknown (Model) (Qed:2ms) + Model i = 0 + Model j = 0 + Model Offset = 0 + [...] +\end{shell} + +User probes are always reported, even if \verb+-wp-counter-examples+ is not +activated. This feature reveals to be extremely useful for debugging proofs in +practice, even when counter examples is not available or not activated. It also +helps in understanding \textsf{C}, \textsf{ACSL} and \textsf{WP} models and +encoding. + +You may freely add as many probes as you want: probes do not modify the +proof obligations submitted to the provers, unless \verb+-wp-counter-examples+ +is activated. For instance, in the previous example: + +\begin{shell} + # frama-c [...] -wp-status -wp-no-counter-examples + [...] + Goal Post-condition (file /Users/correnson/work/matrix.i, line 3) in 'value': + Assume { + Type: is_sint32(i) /\ is_sint32(j). + Probe Offset = ((4 * j) + (20 * i)) / 4. + } + Prove: false. + Prover CVC4 1.8 returns Unknown (Qed:2ms) (Cached) + [...] +\end{shell} + +\noindent +Summary of \textsf{WP} options related to probes and counter-examples generation: +\begin{description} +\item[\tt -wp-(no)-counter-examples] enables counter examples generation for + supporting provers. Also generate probes for formal parameters of functions + and universal quantifiers of lemmas. Not set by default. +\item[\tt -wp-print, -wp-status] pretty-prints the probes and the associated models, + when available. +\item[\tt -wp-solver xxx-ce] use the specified variant of prover, even + when counter-examples is not activated. +\item[\tt -wp-(no)-probes] registers the \verb+@probe+ \textsf{ACSL} + extension in \textsf{Frama-C} kernel. + This option is set by default, but you may want to deactivate it to + avoid any clash with extensions from other plugins. +\end{description} + \subsection{Additional Proof Libraries} \label{prooflibs} diff --git a/src/plugins/wp/gui/GuiList.ml b/src/plugins/wp/gui/GuiList.ml index 149c4edbef0dc558565a89822d19d9d30741ea77..4d694a5326392e5fe2dd00b9ef54aafaebbe81ad 100644 --- a/src/plugins/wp/gui/GuiList.ml +++ b/src/plugins/wp/gui/GuiList.ml @@ -54,6 +54,7 @@ let render_prover_result p = let icon_of_verdict ~smoke = function | NoResult -> icn_none | Valid -> if smoke then icn_invalid else icn_valid + | Invalid -> if smoke then icn_valid else icn_invalid | Unknown -> if smoke then icn_valid else icn_unknown | Timeout | Stepout -> if smoke then icn_valid else icn_cut | Failed -> icn_failed diff --git a/src/plugins/wp/gui/GuiProof.ml b/src/plugins/wp/gui/GuiProof.ml index df6db9048c1dc7a2a9789be508ae8a159f649d31..390f2a7e2759f93d4e69ca0affb209e7d954e2fd 100644 --- a/src/plugins/wp/gui/GuiProof.ml +++ b/src/plugins/wp/gui/GuiProof.ml @@ -60,7 +60,7 @@ class printer (text : Wtext.text) = text#printf "@{<bf>Prover@} %a: @{<green>%a@}.@\n" VCS.pp_prover prv VCS.pp_result res else - text#printf "@{<bf>Prover@} %a: @{<green>%a@}.@\n" + text#printf "@{<bf>Prover@} %a: @{<orange>%a@}.@\n" VCS.pp_prover prv VCS.pp_result res ) (Wpo.get_results wpo) diff --git a/src/plugins/wp/gui/GuiProver.ml b/src/plugins/wp/gui/GuiProver.ml index 64c87a6af4697dc4610f133e103f0116eb1b6600..792592208198d06176cf9f9384d32aec36bfc960 100644 --- a/src/plugins/wp/gui/GuiProver.ml +++ b/src/plugins/wp/gui/GuiProver.ml @@ -78,7 +78,9 @@ class prover ~(console:Wtext.text) ~prover = let lerr = Filepath.exists ferr in if lout || lerr then console#hrule ; console#scroll () ; - console#printf "[%a] %a@." VCS.pp_prover prover VCS.pp_result res ; + console#printf "[%a] %a@.%a" VCS.pp_prover prover + VCS.pp_result res + VCS.pp_model res.prover_model ; if lout then Command.pp_from_file console#fmt fout ; if lerr then Command.pp_from_file console#fmt ferr ; if lout || lerr then console#hrule ; @@ -134,7 +136,7 @@ class prover ~(console:Wtext.text) ~prover = self#set_action ~tooltip:"Run Prover" ~icon:`MEDIA_PLAY ~callback () ; Pretty_utils.ksfprintf self#set_label "%a (%a)" VCS.pp_prover prover Rformat.pp_time res.VCS.prover_time ; - | VCS.Unknown | VCS.Timeout | VCS.Stepout -> + | VCS.Unknown | VCS.Timeout | VCS.Stepout | VCS.Invalid -> let callback () = self#run wpo in self#set_status ko_status ; self#set_action ~tooltip:"Run Prover" ~icon:`MEDIA_PLAY ~callback () ; diff --git a/src/plugins/wp/mcfg.ml b/src/plugins/wp/mcfg.ml index 4a7d416a81469a627be953e577d2ffdfc00f8074..d746f4ba4aace5dc43ef1c9d87c8c318aa08e200 100644 --- a/src/plugins/wp/mcfg.ml +++ b/src/plugins/wp/mcfg.ml @@ -82,6 +82,9 @@ module type S = sig val new_env : ?lvars:Cil_types.logic_var list -> kernel_function -> t_env val add_axiom : WpPropId.prop_id -> LogicUsage.logic_lemma -> unit + + val add_probe : t_env -> ?stmt:stmt -> string -> term -> t_prop -> t_prop + val add_hyp : ?for_pid:WpPropId.prop_id -> t_env -> WpPropId.pred_info -> t_prop -> t_prop val add_goal : t_env -> WpPropId.pred_info -> t_prop -> t_prop diff --git a/src/plugins/wp/ptip.ml b/src/plugins/wp/ptip.ml index 8254af45dec226d150a2aff778224021c2318ca1..76c0393e81500d32f4836c81862b01646a846117 100644 --- a/src/plugins/wp/ptip.ml +++ b/src/plugins/wp/ptip.ml @@ -151,6 +151,7 @@ class autofocus = match step.condition with | When _ -> true | State s -> self#occurs_state s + | Probe(_,t) -> self#occurs_term t | Init p | Have p | Type p | Core p -> self#occurs_term (F.e_prop p) | Branch(p,sa,sb) -> @@ -246,12 +247,13 @@ class autofocus = then Tactical.(Inside(Goal goal,a)) else let pool = ref Tactical.Empty in - let lookup_pred s a p = - if F.is_subterm a (F.e_prop p) then + let lookup_term s a t = + if F.is_subterm a t then begin pool := Tactical.(Inside(Step s,a)); raise Exit; end in + let lookup_pred s a p = lookup_term s a (F.e_prop p) in let rec lookup_sequence a hs = (*TODO: staged iter *) Conditions.iter @@ -259,6 +261,7 @@ class autofocus = match step.condition with | (Type p | Init p | Have p | When p | Core p) -> lookup_pred step a p + | Probe(_,t) -> lookup_term step a t | Branch(p,sa,sb) -> lookup_pred step a p ; lookup_sequence a sa ; @@ -385,6 +388,8 @@ class pcond begin match step.condition with | State _ -> () + | Probe(_,t) -> + domain <- Vars.union (F.vars t) domain | Have p | Init p | Core p | When p | Type p -> domain <- Vars.union (F.varsp p) domain | Branch(p,a,b) -> diff --git a/src/plugins/wp/register.ml b/src/plugins/wp/register.ml index 2805cea703ab916916177edd3b987d6f41ea8f09..eca897451ea5a8d0498ad46dc1526b67ada578f2 100644 --- a/src/plugins/wp/register.ml +++ b/src/plugins/wp/register.ml @@ -354,6 +354,20 @@ let do_wpo_result goal prover res = if VCS.is_verdict res && prover = VCS.Qed then do_progress goal "Qed" +let pp_hasmodel fmt goal = + if Wp_parameters.CounterExamples.get () then + let results = Wpo.get_results goal in + let model = + List.exists + (fun (_,r) -> not @@ Probe.Map.is_empty r.VCS.prover_model) + results in + if model then Format.fprintf fmt " (Model)" else + let ce_variant = + List.exists + (fun (p,_) -> VCS.has_counter_examples p) + results in + if ce_variant then Format.fprintf fmt " (No Model)" + let do_report_stats ~shell ~cache ~smoke goal (stats : Stats.stats) = let status = if smoke then @@ -361,25 +375,27 @@ let do_report_stats ~shell ~cache ~smoke goal (stats : Stats.stats) = | Valid -> "[Failed] (Doomed)" | Failed -> "[Failure] (Solver Error)" | NoResult | Computing _ -> "[NoResult] (Unknown)" - | (Unknown | Timeout | Stepout) when shell -> "[Passed] (Unsuccess)" + | (Unknown | Timeout | Stepout | Invalid) + when shell -> "[Passed] (Unsuccess)" | Unknown -> "[Passed] (Unknown)" | Timeout -> "[Passed] (Timeout)" | Stepout -> "[Passed] (Stepout)" + | Invalid -> "[Passed] (Invalid)" else match stats.best with | NoResult when shell -> "[NoResult]" | NoResult | Computing _ -> "" | Valid -> "[Valid]" | Failed -> "[Failure]" - | (Unknown | Timeout | Stepout) when shell -> "[Unsuccess]" + | (Invalid | Unknown | Timeout | Stepout) when shell -> "[Unsuccess]" | Unknown -> "[Unknown]" | Timeout -> "[Timeout]" | Stepout -> "[Stepout]" - in - if status <> "" then - Wp_parameters.feedback "%s %s%a%a" + | Invalid -> "[Invalid]" + in if status <> "" then + Wp_parameters.feedback "%s %s%a%a%a" status (Wpo.get_gid goal) (Stats.pp_stats ~shell ~cache) stats - pp_warnings goal + pp_hasmodel goal pp_warnings goal let do_wpo_success ~shell ~cache goal success = if Wp_parameters.Generate.get () then diff --git a/src/plugins/wp/tests/ptests_config b/src/plugins/wp/tests/ptests_config index d53499bf328b942033d55884fabd3a78cb101437..5221e8c0f16237b724d4e1a878776e1b22e8ac88 100644 --- a/src/plugins/wp/tests/ptests_config +++ b/src/plugins/wp/tests/ptests_config @@ -1,6 +1,8 @@ -DEFAULT_SUITES= wp wp_acsl wp_plugin wp_bts wp_store wp_hoare +DEFAULT_SUITES= wp wp_acsl wp_plugin wp_ce wp_bts wp_store wp_hoare DEFAULT_SUITES= wp_typed wp_usage wp_gallery wp_manual wp_region wp_tip qualif_SUITES= wp wp_acsl wp_plugin wp_bts wp_store wp_hoare qualif_SUITES= wp_typed wp_usage wp_gallery wp_manual wp_region wp_tip qualif_SUITES= why3 + +ce_SUITES= wp_ce diff --git a/src/plugins/wp/tests/wp_ce/cvc4_ce.i b/src/plugins/wp/tests/wp_ce/cvc4_ce.i new file mode 100644 index 0000000000000000000000000000000000000000..cff74da54d769ae6297aaf89277bb77ef8f7a903 --- /dev/null +++ b/src/plugins/wp/tests/wp_ce/cvc4_ce.i @@ -0,0 +1,12 @@ +/* run.config_ce + OPT: -wp -wp-counter-examples -wp-prover cvc4 -wp-status +*/ +/* run.config_qualif + DONTRUN: +*/ + + +//@ check lemma wrong: \forall integer x; \abs(x) == x; + +//@ ensures \result == \abs(x); assigns \nothing; +int wrong(int x) { return x; } diff --git a/src/plugins/wp/tests/wp_ce/oracle/cvc4_ce.res.oracle b/src/plugins/wp/tests/wp_ce/oracle/cvc4_ce.res.oracle new file mode 100644 index 0000000000000000000000000000000000000000..c98d32bed6cbbdca6bf035c488bd49c632813e4f --- /dev/null +++ b/src/plugins/wp/tests/wp_ce/oracle/cvc4_ce.res.oracle @@ -0,0 +1,28 @@ +# frama-c -wp [...] +[kernel] Parsing cvc4_ce.i (no preprocessing) +[wp] Running WP plugin... +[wp] [Valid] Goal wrong_exits (Cfg) (Unreachable) +[wp] [Valid] Goal wrong_terminates (Cfg) (Trivial) +[wp] Warning: Missing RTE guards +------------------------------------------------------------ + Global +------------------------------------------------------------ + +Goal Check Lemma 'wrong': +Prove: IAbs.abs(x) = x. + +------------------------------------------------------------ +------------------------------------------------------------ + Function wrong +------------------------------------------------------------ + +Goal Post-condition (file cvc4_ce.i, line 11) in 'wrong': +Assume { Type: is_sint32(wrong_0). } +Prove: IAbs.abs(wrong_0) = wrong_0. + +------------------------------------------------------------ + +Goal Assigns nothing in 'wrong': +Prove: true. + +------------------------------------------------------------ diff --git a/src/plugins/wp/tests/wp_ce/oracle/probes.res.oracle b/src/plugins/wp/tests/wp_ce/oracle/probes.res.oracle new file mode 100644 index 0000000000000000000000000000000000000000..cc0cb7183aad041b65eb3c24fdea44633f5bc363 --- /dev/null +++ b/src/plugins/wp/tests/wp_ce/oracle/probes.res.oracle @@ -0,0 +1,50 @@ +# frama-c -wp [...] +[kernel] Parsing probes.i (no preprocessing) +[wp] Running WP plugin... +[wp] [Valid] Goal get_exits (Cfg) (Unreachable) +[wp] [Valid] Goal get_terminates (Cfg) (Trivial) +[wp] Warning: Missing RTE guards +------------------------------------------------------------ + Function get +------------------------------------------------------------ + +Goal Post-condition (file probes.i, line 12) in 'get': +Let x = -i_2. +Assume { + Type: is_sint32(i) /\ is_sint32(i_1) /\ is_sint32(i_2) /\ is_sint32(i_3) /\ + is_sint32(i_4) /\ is_sint32(j) /\ is_sint32(k). + If i_2 != 0 + Then { Have: (1 + i_2) = i_3. } + Else { Have: i_3 = 0. } + If j != 0 + Then { Have: i_4 = i_3. Have: (1 + i_3) = i_1. } + Else { Have: i_3 = i_1. } + If k != 0 + Then { Have: (1 + i_1) = i. } + Else { Have: i_1 = i. } + Probe A = 0. + Probe B = i_3 - i_2. + Probe C = i_1 - i_2. + Probe Offset = ((4 * k) + (20 * j) + (80 * i)) / 4. +} +Prove: false. + +------------------------------------------------------------ + +Goal Assigns nothing in 'get' (1/3): +Effect at line 16 +Prove: true. + +------------------------------------------------------------ + +Goal Assigns nothing in 'get' (2/3): +Effect at line 18 +Prove: true. + +------------------------------------------------------------ + +Goal Assigns nothing in 'get' (3/3): +Effect at line 20 +Prove: true. + +------------------------------------------------------------ diff --git a/src/plugins/wp/tests/wp_ce/oracle_ce/cvc4_ce.res.oracle b/src/plugins/wp/tests/wp_ce/oracle_ce/cvc4_ce.res.oracle new file mode 100644 index 0000000000000000000000000000000000000000..ab352704f50f31c4760de5dc3ad56b17d932c8d8 --- /dev/null +++ b/src/plugins/wp/tests/wp_ce/oracle_ce/cvc4_ce.res.oracle @@ -0,0 +1,44 @@ +# frama-c -wp [...] +[kernel] Parsing cvc4_ce.i (no preprocessing) +[wp] Running WP plugin... +[wp] [Valid] Goal wrong_exits (Cfg) (Unreachable) +[wp] [Valid] Goal wrong_terminates (Cfg) (Trivial) +[wp] Warning: Missing RTE guards +[wp] 3 goals scheduled +[wp] [Unsuccess] typed_check_lemma_wrong (CVC4) (Model) +[wp] [Unsuccess] typed_wrong_ensures (CVC4) (Model) +[wp] [Valid] typed_wrong_assigns (Qed) +[wp] Proved goals: 3 / 5 + Terminating: 1 + Unreachable: 1 + Qed: 1 + Unsuccess: 2 +------------------------------------------------------------ + Global +------------------------------------------------------------ + +Goal Check Lemma 'wrong': +Assume { Probe x = x. } +Prove: IAbs.abs(x) = x. +Prover CVC4 returns Unsuccess (Model) +Model x = -1 + +------------------------------------------------------------ +------------------------------------------------------------ + Function wrong +------------------------------------------------------------ + +Goal Post-condition (file cvc4_ce.i, line 11) in 'wrong': +Assume { Type: is_sint32(wrong_0). Probe x = wrong_0. } +Prove: IAbs.abs(wrong_0) = wrong_0. +Prover CVC4 returns Unsuccess (Model) +Model x = -1 + +------------------------------------------------------------ +------------------------------------------------------------ + Axiomatics WP Alt-Ergo Total Success + Lemma - - 1 0.0% +------------------------------------------------------------ + Functions WP Alt-Ergo Total Success + wrong 1 - 2 50.0% +------------------------------------------------------------ diff --git a/src/plugins/wp/tests/wp_ce/probes.i b/src/plugins/wp/tests/wp_ce/probes.i new file mode 100644 index 0000000000000000000000000000000000000000..03ccb78a4337863619222551aeefb45cf07fbdbf --- /dev/null +++ b/src/plugins/wp/tests/wp_ce/probes.i @@ -0,0 +1,26 @@ +/* run.config_qualif + DONTRUN: +*/ +/* run.config_ce + DONTRUN: +*/ + +// Matrix addessing + +int a[3][4][5]; + +//@ ensures \false; assigns \nothing; +void get(int i, int j, int k) +{ + A: + if (i) i++; + B: + if (j) i++; + C: + if (k) i++; + //@ probe A: \at(i,A) - \at(i,Pre); + //@ probe B: \at(i,B) - \at(i,Pre); + //@ probe C: \at(i,C) - \at(i,Pre); + //@ probe Offset: &a[i][j][k] - &a[0][0][0]; + return; +} diff --git a/src/plugins/wp/tests/wp_ce/test_config_ce b/src/plugins/wp/tests/wp_ce/test_config_ce new file mode 100644 index 0000000000000000000000000000000000000000..c3e8d3e1aaf369e619027b1cb1ee1b33eb789d0b --- /dev/null +++ b/src/plugins/wp/tests/wp_ce/test_config_ce @@ -0,0 +1,3 @@ +PLUGIN: wp +CMD: @frama-c@ -wp -wp-par 1 -wp-msg-key shell -wp-warn-key pedantic-assigns=inactive -wp-report %{dep:@PTEST_SUITE_DIR@/../qualif.report} -wp-cache none +OPT: diff --git a/src/plugins/wp/wpApi.ml b/src/plugins/wp/wpApi.ml index 26910b42448f6a4b5ed06ceec7f6e9358b5c85b6..fb612438d63376095cbad85324c2ff2f114cdbd3 100644 --- a/src/plugins/wp/wpApi.ml +++ b/src/plugins/wp/wpApi.ml @@ -125,6 +125,7 @@ struct `String begin match verdict with | Valid -> if smoke then "DOOMED" else "VALID" + | Invalid -> if smoke then "PASSED" else "INVALID" | Unknown -> if smoke then "PASSED" else "UNKNOWN" | Timeout -> if smoke then "PASSED" else "TIMEOUT" | Stepout -> if smoke then "PASSED" else "STEPOUT" diff --git a/src/plugins/wp/wpReport.ml b/src/plugins/wp/wpReport.ml index 32ab774e70e187994330774435391bb5b18935cf..893a7df45fbc9bdf77c46c67e55a790851614a71 100644 --- a/src/plugins/wp/wpReport.ml +++ b/src/plugins/wp/wpReport.ml @@ -97,7 +97,7 @@ let result ~status ~smoke (r:VCS.result) = match r.VCS.verdict with | VCS.NoResult | VCS.Computing _ -> NORESULT | VCS.Failed -> INCONCLUSIVE - | VCS.Unknown | VCS.Timeout | VCS.Stepout -> + | VCS.Unknown | VCS.Timeout | VCS.Stepout | VCS.Invalid -> if smoke then VALID else UNSUCCESS | VCS.Valid -> if smoke then UNSUCCESS else VALID diff --git a/src/plugins/wp/wp_parameters.ml b/src/plugins/wp/wp_parameters.ml index fa160ed4b0afc590811632467727bed6a8503a56..aa558367890b812aec77a6c21dd81f4f8ec083f6 100644 --- a/src/plugins/wp/wp_parameters.ml +++ b/src/plugins/wp/wp_parameters.ml @@ -1152,6 +1152,20 @@ module OutputDir = Defaults to some temporary directory." end) +module Probes = + True + (struct + let option_name = "-wp-probes" + let help = "Activate user-defines probes (@probe) (default: yes)" + end) + +module CounterExamples = + False + (struct + let option_name = "-wp-counter-examples" + let help = "Ask for counter examples when supported by provers" + end) + (* -------------------------------------------------------------------------- *) (* --- Output Dir --- *) (* -------------------------------------------------------------------------- *) diff --git a/src/plugins/wp/wp_parameters.mli b/src/plugins/wp/wp_parameters.mli index ee692cfe9f0d8bdc3b200d23a2c1261db960caca..02ddb77250bba27096a59804668363c1282a48a6 100644 --- a/src/plugins/wp/wp_parameters.mli +++ b/src/plugins/wp/wp_parameters.mli @@ -164,6 +164,8 @@ module SmokeDeadcode: Parameter_sig.Bool module SmokeDeadcall: Parameter_sig.Bool module SmokeDeadlocalinit: Parameter_sig.Bool module SmokeDeadloop: Parameter_sig.Bool +module Probes: Parameter_sig.Bool +module CounterExamples: Parameter_sig.Bool (** {2 Getters} *) diff --git a/src/plugins/wp/wpo.ml b/src/plugins/wp/wpo.ml index 578e5b974f3d8f39ccf13924c980f9e9e54d1758..94995e8bb72c47a7ee64e08cdbac4866b01a4fce 100644 --- a/src/plugins/wp/wpo.ml +++ b/src/plugins/wp/wpo.ml @@ -109,7 +109,9 @@ struct mutable time : float ; mutable simplified : bool ; mutable sequent : Conditions.sequent ; - mutable obligation : F.pred ; + mutable opened : F.pred ; + mutable closed : F.pred ; + mutable probes : F.term Probe.Map.t ; } let empty = Conditions.empty @@ -118,21 +120,27 @@ struct time = 0.0 ; simplified = false ; sequent = empty , F.p_false ; - obligation = F.p_false ; + opened = F.p_false ; + closed = F.p_false ; + probes = Probe.Map.empty ; } let trivial = { time = 0.0 ; simplified = true ; sequent = empty , F.p_true ; - obligation = F.p_true ; + opened = F.p_true ; + closed = F.p_true ; + probes = Probe.Map.empty ; } let make sequent = { time = 0.0 ; simplified = false ; sequent = sequent ; - obligation = F.p_false ; + opened = F.p_false ; + closed = F.p_false ; + probes = Probe.Map.empty ; } let is_computed g = g.simplified @@ -178,9 +186,12 @@ struct if Wp_parameters.Clean.get () then apply "-wp-clean" Conditions.clean g ; end ; - if Conditions.is_trivial g.sequent then - g.sequent <- Conditions.trivial ; - g.obligation <- Conditions.close g.sequent + begin + if Conditions.is_trivial g.sequent then + g.sequent <- Conditions.trivial ; + g.opened <- Conditions.property g.sequent ; + g.closed <- F.p_close g.opened ; + end let safecompute ~pid g = begin @@ -191,6 +202,7 @@ struct Wp_parameters.debug ~dkey "Simplification time: %a" Rformat.pp_time !timer ; g.time <- !timer ; + g.probes <- Conditions.probes @@ fst g.sequent ; end let compute ~pid g = @@ -198,7 +210,9 @@ struct Lang.local ~vars:(Conditions.vars_seq g.sequent) (safecompute ~pid) g - let compute_proof ~pid g = compute ~pid g ; g.obligation + let compute_proof ~pid ?(opened=false) g = + compute ~pid g ; if opened then g.opened else g.closed + let compute_probes ~pid g = compute ~pid g ; g.probes let compute_descr ~pid g = compute ~pid g ; g.sequent let get_descr g = g.sequent let qed_time g = g.time @@ -286,7 +300,9 @@ struct if result.verdict <> NoResult then Format.fprintf fmt "Prover %a returns %t@\n" pp_prover prover - (pp_result_qualif prover result) + (pp_result_qualif prover result) ; + if Wp_parameters.CounterExamples.get () then + pp_model fmt result.prover_model ) results ; end @@ -847,12 +863,6 @@ let goals_of_property prop = in WPOset.elements poset -(* -------------------------------------------------------------------------- *) -(* --- Prover and Files --- *) -(* -------------------------------------------------------------------------- *) - -let get_model w = w.po_model - (* -------------------------------------------------------------------------- *) (* --- Generators --- *) (* -------------------------------------------------------------------------- *) diff --git a/src/plugins/wp/wpo.mli b/src/plugins/wp/wpo.mli index c23d0a30f8cbe7d23adf0538c99e54cfb3235e4e..6838763a3c5c734aff5bff71246636ddc1b80c0c 100644 --- a/src/plugins/wp/wpo.mli +++ b/src/plugins/wp/wpo.mli @@ -48,8 +48,9 @@ sig val is_computed : t -> bool val make : Conditions.sequent -> t val compute : pid:WpPropId.prop_id -> t -> unit - val compute_proof : pid:WpPropId.prop_id -> t -> F.pred + val compute_proof : pid:WpPropId.prop_id -> ?opened:bool -> t -> F.pred val compute_descr : pid:WpPropId.prop_id -> t -> Conditions.sequent + val compute_probes : pid:WpPropId.prop_id -> t -> F.term Probe.Map.t val get_descr : t -> Conditions.sequent val qed_time : t -> float end