diff --git a/src/plugins/wp/LogicSemantics.ml b/src/plugins/wp/LogicSemantics.ml index 654ecfa161c9ec36f57379ff101be547cecbecc1..06322b509861441f6af05e12df251c5162ce0a6d 100644 --- a/src/plugins/wp/LogicSemantics.ml +++ b/src/plugins/wp/LogicSemantics.ml @@ -807,6 +807,23 @@ struct let addrs = C.logic env t in p_all (L.initialized sigma) (L.region (Ctypes.object_of te) addrs) + let call_pred env f ls es = + match C.logic_info env f with + | Some p -> + if ls <> [] || es <> [] then + Warning.error "Unexpected parameters for named predicate '%a'" + Logic_info.pretty f ; p + | None -> + let empty ls = + if ls <> [] then + Warning.error "Unexpected labels for purely logic '%a'" + Logic_info.pretty f ; + in + match LogicBuiltins.logic f with + | ACSLDEF -> C.call_pred env f ls es + | HACK phi -> empty ls ; F.p_bool (phi es) + | LFUN p -> empty ls ; p_call p es + let predicate polarity env p = match p.pred_content with | Pfalse -> p_false @@ -833,24 +850,7 @@ struct | _ -> Warning.error "\\subset requires 2 arguments" end | Papp(f,ls,ts) -> - begin - match C.logic_info env f with - | Some p -> - if ls <> [] || ts <> [] then - Warning.error "Unexpected parameters for named predicate '%a'" - Logic_info.pretty f ; p - | None -> - let empty ls = - if ls <> [] then - Warning.error "Unexpected labels for purely logic '%a'" - Logic_info.pretty f ; - in - let es = List.map (val_of_term env) ts in - match LogicBuiltins.logic f with - | ACSLDEF -> C.call_pred env f ls es - | HACK phi -> empty ls ; F.p_bool (phi es) - | LFUN p -> empty ls ; p_call p es - end + call_pred env f ls @@ List.map (val_of_term env) ts | Plet( { l_var_info=v ; l_body=LBterm a } , p ) -> let va = C.logic env a in diff --git a/src/plugins/wp/Sigs.mli b/src/plugins/wp/Sigs.mli index 83335aaf4119b97eb8924ec181f00f201c45c44b..e8a2b331fbb88c39682c3ed6910505d9c12ae2ca 100644 --- a/src/plugins/wp/Sigs.mli +++ b/src/plugins/wp/Sigs.mli @@ -796,6 +796,11 @@ sig underlying memory model. *) val pred : polarity -> env -> Cil_types.predicate -> pred + (** Compile a predicate call. *) + val call_pred: + env -> Cil_types.logic_info -> Cil_types.logic_label list -> term list -> + pred + (** Compile a term representing a set of memory locations into an abstract region. *) val region : env -> Cil_types.term -> region diff --git a/src/plugins/wp/cfgAnnot.ml b/src/plugins/wp/cfgAnnot.ml index 044184ea98f9c62e27ed682b0f220ed913f5e797..d6bc77f086a4b5a1202344190d8a0425ff607b53 100644 --- a/src/plugins/wp/cfgAnnot.ml +++ b/src/plugins/wp/cfgAnnot.ml @@ -250,6 +250,9 @@ let get_decreases_goal kf = | None -> None | Some v -> Some (defined v) +let get_decreases_hyp kf = + Annotations.decreases ~populate:false kf + (* -------------------------------------------------------------------------- *) (* --- Contracts --- *) (* -------------------------------------------------------------------------- *) @@ -262,6 +265,7 @@ type contract = { contract_smoke : WpPropId.pred_info list ; contract_assigns : Cil_types.assigns ; contract_terminates : bool * Cil_types.predicate ; + contract_decreases : Cil_types.variant option ; } let assigns_upper_bound behaviors = @@ -338,6 +342,7 @@ module CallContract = WpContext.StaticGenerator(Kernel_function) | Writes froms -> Writes (normalize_froms Normal froms) in let terminates = get_terminates_hyp kf in + let decreases = get_decreases_hyp kf in { contract_cond = List.rev !wcond ; contract_hpre = List.rev !whpre ; @@ -346,6 +351,7 @@ module CallContract = WpContext.StaticGenerator(Kernel_function) contract_smoke = [] ; contract_assigns = assigns ; contract_terminates = terminates ; + contract_decreases = decreases ; } end) diff --git a/src/plugins/wp/cfgAnnot.mli b/src/plugins/wp/cfgAnnot.mli index 53c1c4659e4eee89d24c38360ef7b054af202a9d..adfbe70680a6c211384c0e3d799a2ac57b478dba 100644 --- a/src/plugins/wp/cfgAnnot.mli +++ b/src/plugins/wp/cfgAnnot.mli @@ -118,6 +118,7 @@ type contract = { contract_smoke : pred_info list ; contract_assigns : assigns ; contract_terminates : bool * predicate ; (* boolean: assumed terminates *) + contract_decreases : variant option ; } val get_call_contract : ?smoking:stmt -> kernel_function -> stmt -> contract diff --git a/src/plugins/wp/cfgCalculus.ml b/src/plugins/wp/cfgCalculus.ml index 4fba7b07405c29adbe274cefd5e4695299d724c2..c7a4a5d656b4f2e1dc93e8e75671ae3035fd1cbe 100644 --- a/src/plugins/wp/cfgCalculus.ml +++ b/src/plugins/wp/cfgCalculus.ml @@ -188,7 +188,7 @@ struct let prove_property env (p : WpPropId.pred_info) w = if is_selected ~goal:true env p then W.add_goal env.we p w else w - let prove_subproperty env (p : WpPropId.pred_info) ?deps prop stmt source w = + let prove_subproperty env p ?deps prop stmt source w = if is_selected ~goal:true env p then W.add_subgoal env.we ?deps p prop stmt source w else w @@ -367,15 +367,31 @@ struct W.call_goal_precond env.we s kf es ~pre w_call else w_call in - match env.terminates with - | Some p when is_default_bhv env.mode && is_selected ~goal:true env p -> - let generated, callee_t = c.contract_terminates in - if generated then - Wp_parameters.warning ~once:true - "Missing terminates clause on call to %a, defaults to %a" - Kernel_function.pretty kf Cil_printer.pp_predicate callee_t ; - W.call_terminates env.we p s kf es ~callee_t w_pre - | _ -> w_pre + let callee_t = + (** TODO when kernel terminates complete: remove this code. *) + let generated, callee_t = c.contract_terminates in + if generated && env.terminates <> None then + Wp_parameters.warning ~once:true + "Missing terminates clause on call to %a, defaults to %a" + Kernel_function.pretty kf Cil_printer.pp_predicate callee_t ; + Some callee_t + in + let selected t = is_selected ~goal:true env t && is_default_bhv env.mode in + let in_cluster = CfgInfos.in_cluster ~caller:env.mode.kf kf in + let w_term = match env.terminates with + | Some t when selected t -> + W.call_terminates env.we s kf es t ?callee_t w_pre + | _ -> w_pre + in + let w_decr = match env.decreases with + | Some d when selected d && in_cluster -> + W.call_decreases env.we s kf es d + ?caller_t:(Option.map snd env.terminates) + ?callee_d:c.contract_decreases + w_term + | _ -> w_term + in + w_decr let do_complete_disjoint env w = if not (is_default_bhv env.mode) then w diff --git a/src/plugins/wp/cfgDump.ml b/src/plugins/wp/cfgDump.ml index c38b30c4127d6bf3949afa58487cfdeaaab2c9c4..6b348971f63e6ac6627e1b827337de79d0c9fc12 100644 --- a/src/plugins/wp/cfgDump.ml +++ b/src/plugins/wp/cfgDump.ml @@ -111,13 +111,12 @@ let add_goal env (pid,pred) k = Format.fprintf !out " %a -> %a [ style=dotted ] ;@." pretty u pretty k ; merge env u k -let add_subgoal env (pid, t_pred) ?deps prop stmt _source k = +let add_subgoal env (pid, _) ?deps prop stmt _source k = ignore deps ; let u = node () in if Wp_parameters.debug_atleast 1 then - Format.fprintf !out " %a [ color=red , label=\"Prove %a @@ %a (%a)\" ] ;@." + Format.fprintf !out " %a [ color=red , label=\"Prove %a (%a)\" ] ;@." pretty u - Printer.pp_predicate t_pred Cil_printer.pp_stmt stmt Printer.pp_predicate prop else @@ -245,16 +244,49 @@ let call_goal_precond env _stmt kf _es ~pre k = Format.fprintf !out " %a -> %a [ style=dotted ] ;@." pretty u pretty k ; merge env u k -let call_terminates env (_gpid, prop) _stmt kf _es ~callee_t k = - let _ = callee_t in +let call_terminates env _stmt kf _es (_gpid, prop) ?callee_t k = let u = node () in - Format.fprintf !out " %a [ color=red , label=\"Prove PreCond %a%t\" ] ;@." + let pp_opt_pred = Pretty_utils.pp_opt ~none:"FALSE" Printer.pp_predicate in + Format.fprintf !out " %a [ color=red , label=\"Prove Terminates %a%t\" ] ;@." pretty u Kernel_function.pretty kf begin fun fmt -> if Wp_parameters.debug_atleast 1 then Format.fprintf fmt "\n@[<hov 2>Terminates if %a[%s] ==> %a[%a];@]" Printer.pp_predicate prop "Caller" - Printer.pp_predicate callee_t Kernel_function.pretty kf + pp_opt_pred callee_t + Kernel_function.pretty kf + end ; + Format.fprintf !out " %a -> %a [ style=dotted ] ;@." pretty u pretty k ; + merge env u k + +let call_decreases env _s kf _es (_id, (caller_d, rel)) ?caller_t ?callee_d k = + let u = node () in + let pp_opt_pred = Pretty_utils.pp_opt ~none:"TRUE" Printer.pp_predicate in + let pp_rel fmt callee_d = + match rel with + | None -> + Format.fprintf fmt "%a ==> %a[%a] < %a[%s] && %a[%a] >= 0" + pp_opt_pred caller_t + Printer.pp_term callee_d Kernel_function.pretty kf + Printer.pp_term caller_d "Caller" + Printer.pp_term callee_d Kernel_function.pretty kf + | Some rel -> + Format.fprintf fmt "%a ==> %a(%a[%s], %a[%a])" + pp_opt_pred caller_t + Printer.pp_logic_info rel + Printer.pp_term caller_d "Caller" + Printer.pp_term callee_d Kernel_function.pretty kf + in + Format.fprintf !out " %a [ color=red , label=\"Prove Decreases %a%t\" ] ;@." + pretty u Kernel_function.pretty kf + begin fun fmt -> + match callee_d with + | None -> + Format.fprintf fmt "\n@[<hov 2>Decreases if %a ==> FALSE;@]" + pp_opt_pred caller_t + | Some (callee_d, _) -> + if Wp_parameters.debug_atleast 1 then + Format.fprintf fmt "\n@[<hov 2>Decreases if %a;@]" pp_rel callee_d end ; Format.fprintf !out " %a -> %a [ style=dotted ] ;@." pretty u pretty k ; merge env u k diff --git a/src/plugins/wp/cfgInfos.ml b/src/plugins/wp/cfgInfos.ml index c3fa404c7e5b3c500ed9b1893c1f0c304c660950..fb4d49fc6e481c6cfbcf41ad6aa1915b6e587180 100644 --- a/src/plugins/wp/cfgInfos.ml +++ b/src/plugins/wp/cfgInfos.ml @@ -208,7 +208,7 @@ module Callees = WpContext.StaticGenerator(Kernel_function) module RecursiveClusters : sig val is_recursive : Kernel_function.t -> bool - val get_cluster : Kernel_function.t -> Kernel_function.Set.t option + val in_cluster : caller:Kernel_function.t -> Kernel_function.t -> bool end = struct (* Tarjan's algorithm, adapted from: @@ -287,10 +287,15 @@ struct let is_recursive kf = (* in a recursive cluster or contains unspecified function pointer call *) None <> get_cluster kf || [] <> snd @@ Callees.get kf + + let in_cluster ~caller callee = + match get_cluster caller with + | None -> false + | Some cluster -> Fset.mem callee cluster end let is_recursive = RecursiveClusters.is_recursive -let get_cluster = RecursiveClusters.get_cluster +let in_cluster = RecursiveClusters.in_cluster (* -------------------------------------------------------------------------- *) (* --- No variant loops --- *) @@ -433,11 +438,8 @@ let compile Key.{ kf ; smoking ; bhv ; prop } = Option.iter begin fun (cfg : Cfg.automaton) -> (* Spec Iteration *) - if selected_decreases ~prop kf then - Wp_parameters.warning - "Unsupported clause @decreases in '%a' (ignored)" - Kernel_function.pretty kf ; - if selected_terminates ~prop kf || + if selected_decreases ~prop kf || + selected_terminates ~prop kf || selected_disjoint_complete kf ~bhv ~prop || (List.exists (selected_bhv ~smoking ~bhv ~prop) behaviors) then infos.annots <- true ; diff --git a/src/plugins/wp/cfgInfos.mli b/src/plugins/wp/cfgInfos.mli index ba6256d0099a60679a4f083661cdab56fc8b4761..b3daaf0164e7fc9fc2cb231930df62e82aada1b2 100644 --- a/src/plugins/wp/cfgInfos.mli +++ b/src/plugins/wp/cfgInfos.mli @@ -41,7 +41,7 @@ val unreachable : t -> Cfg.vertex -> bool val terminates_deps : t -> Property.Set.t val is_recursive : Kernel_function.t -> bool -val get_cluster : Kernel_function.t -> Kernel_function.Set.t option +val in_cluster : caller:Kernel_function.t -> Kernel_function.t -> bool val trivial_terminates : int ref diff --git a/src/plugins/wp/cfgWP.ml b/src/plugins/wp/cfgWP.ml index 97c136c1d4f285c2cc0117b7d9dd115951496e90..52ea8a59bff7868bc16adfb6e3df6d9ad284285f 100644 --- a/src/plugins/wp/cfgWP.ml +++ b/src/plugins/wp/cfgWP.ml @@ -1054,47 +1054,137 @@ struct (* --- WP RULE : call terminates --- *) (* -------------------------------------------------------------------------- *) - let call_terminates wenv (gpid, caller_t) stmt kf es ~callee_t wp = + let call_terminates wenv stmt kf args (id, caller_t) ?callee_t wp = in_wenv wenv wp (fun env wp -> - let gid = Gsource(gpid, stmt, FromCall) in let outcome = Warning.catch - ~severe:true ~effect:"Can not prove call termination" + ~severe:true + ~effect:"Considering that call must always terminate" (L.pred `Positive env) caller_t in + let warn, caller_t = match outcome with + | Warning.Failed warn -> warn, p_true + | Warning.Result (warn, p) -> warn, p + in + let prove_terminates ~warn p = + add_vc (Gsource(id, stmt, FromCall)) ~warn (p_imply caller_t p) + in + let sigma = L.current env in + let outcome = Warning.catch + ~severe:true + ~effect:"Considering non terminating callee" + (List.map (C.exp sigma)) args in match outcome with - | Warning.Failed warn -> - let vcs = add_vc gid ~warn p_false wp.vcs in + | Warning.Failed warn2 -> + let warn = W.union warn warn2 in + let vcs = prove_terminates ~warn p_false wp.vcs in + { wp with vcs = vcs } + | Warning.Result(warn2, args) -> + let warn = W.union warn warn2 in + let compile_callee = function + | None -> + Warning.error "No terminates clause for %a" + Kernel_function.pretty kf + | Some callee_t -> + let init = L.mem_at env Clabels.init in + let call = L.call kf args in + let call_e = L.mk_env ~here:sigma () in + let call_f = L.call_pre init call sigma in + L.in_frame call_f (L.pred `Positive call_e) callee_t + in + let outcome = + Warning.catch + ~severe:true ~effect:"Considering non terminating callee" + compile_callee callee_t + in + let warn2, callee_t = match outcome with + | Warning.Failed warn -> warn, p_false + | Warning.Result(warn,callee_t) -> warn, callee_t + in + let warn = W.union warn warn2 in + let vcs = prove_terminates ~warn callee_t wp.vcs in + { wp with vcs = vcs }) + + (* -------------------------------------------------------------------------- *) + (* --- WP RULE : call decreases --- *) + (* -------------------------------------------------------------------------- *) + + let call_decreases wenv stmt kf args (id, caller_d) ?caller_t ?callee_d wp = + in_wenv wenv wp + (fun env wp -> + let compile_caller_t caller_t = + if not @@ Wp_parameters.TerminatesVariantHyp.get () then p_true + else match caller_t with + | None -> p_true + | Some t -> (L.pred `Positive env) t + in + let outcome = Warning.catch + ~severe:true + ~effect:"Considering that call must always decrease" + compile_caller_t caller_t + in + let warn, caller_t = match outcome with + | Warning.Failed warn -> warn, p_true + | Warning.Result (warn, p) -> warn, p + in + let prove_decreases ~warn p = + add_vc (Gsource(id, stmt, FromCall)) ~warn (p_imply caller_t p) + in + let sigma = L.current env in + let outcome = Warning.catch + ~severe:true + ~effect:"Considering non decreasing call" + (List.map (C.exp sigma)) args in + match outcome with + | Warning.Failed warn2 -> + let warn = W.union warn warn2 in + let vcs = prove_decreases ~warn p_false wp.vcs in { wp with vcs = vcs } - | Warning.Result(warn, caller_t) -> - let sigma = L.current env in - let outcome = Warning.catch - ~severe:true ~effect:"Can not prove call preconditions" - (List.map (C.exp sigma)) es in - match outcome with - | Warning.Failed warn2 -> - let warn = W.union warn warn2 in - let vcs = add_vc gid ~warn p_false wp.vcs in - { wp with vcs = vcs } - | Warning.Result(warn2, vs) -> - let warn = W.union warn warn2 in - let init = L.mem_at env Clabels.init in - let call = L.call kf vs in - let call_e = L.mk_env ~here:sigma () in - let call_f = L.call_pre init call sigma in - let outcome = Warning.catch - ~severe:true ~effect:"Can not prove call precondition" - (L.in_frame call_f (L.pred `Positive call_e)) callee_t in - match outcome with - | Warning.Result(warn3,callee_t) -> - let warn = W.union warn warn3 in - let goal = p_imply caller_t callee_t in - let vcs = add_vc gid ~warn goal wp.vcs in - { wp with vcs = vcs } - | Warning.Failed warn3 -> - let warn = W.union warn warn3 in - let vcs = add_vc gid ~warn p_false wp.vcs in - { wp with vcs = vcs }) + | Warning.Result(warn2, args) -> + let warn = W.union warn warn2 in + let init = L.mem_at env Clabels.init in + let call = L.call kf args in + let call_e = L.mk_env ~here:sigma () in + let call_f = L.call_pre init call sigma in + let compile_decreases (caller_d, callee_d) = + match caller_d, callee_d with + | _, None -> + Warning.error "No decreases clause for %a" + Kernel_function.pretty kf + | (_, r), Some (_, r') + when not @@ Option.equal Logic_utils.is_same_logic_info r r' -> + Warning.error + "On call to %a, decreases relation (%a) does not match \ + caller relation (%a)" + Kernel_function.pretty kf + (Pretty_utils.pp_opt Cil_printer.pp_logic_info) r + (Pretty_utils.pp_opt Cil_printer.pp_logic_info) r' + | (caller_d, rel), Some (callee_d,_ ) -> + let rel caller callee = match rel with + | None -> + p_and (p_leq e_zero callee) (p_lt callee caller) + | Some rel -> + (L.in_frame call_f (L.call_pred call_e)) + rel [] [caller ; callee] + in + let caller_d = L.term env caller_d in + let callee_d = + (L.in_frame call_f (L.term call_e)) callee_d in + rel caller_d callee_d + in + let outcome = + Warning.catch + ~severe:true ~effect:"Considering non decreasing call" + compile_decreases (caller_d, callee_d) + in + let warn2, pred = match outcome with + | Warning.Failed warn -> warn, p_false + | Warning.Result (warn, p) -> warn, p + in + let warn = W.union warn warn2 in + let vcs = prove_decreases ~warn pred wp.vcs in + { wp with vcs = vcs }) + (* -------------------------------------------------------------------------- *) (* --- WP RULE : call postcondition --- *) diff --git a/src/plugins/wp/mcfg.mli b/src/plugins/wp/mcfg.mli index 3afad69feed2e4d4e6fcd96525a437f27dfd6306..881d303b667a1abad001661a268c50e40fbb6cab 100644 --- a/src/plugins/wp/mcfg.mli +++ b/src/plugins/wp/mcfg.mli @@ -68,7 +68,7 @@ module type S = sig 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 - val add_subgoal : t_env -> WpPropId.pred_info -> ?deps:Property.Set.t -> + val add_subgoal : t_env -> WpPropId.prop_id * 'a -> ?deps:Property.Set.t -> predicate -> stmt -> WpPropId.effect_source -> t_prop -> t_prop val add_assigns : t_env -> WpPropId.assigns_info -> t_prop -> t_prop @@ -104,9 +104,15 @@ module type S = sig pre: WpPropId.pred_info list -> t_prop -> t_prop - val call_terminates : t_env -> WpPropId.pred_info -> - stmt -> kernel_function -> exp list -> - callee_t:predicate -> t_prop -> t_prop + val call_terminates : t_env -> stmt -> + kernel_function -> exp list -> + WpPropId.pred_info -> ?callee_t:predicate -> t_prop -> t_prop + + val call_decreases : t_env -> stmt -> + kernel_function -> exp list -> + WpPropId.variant_info -> + ?caller_t: predicate -> + ?callee_d: variant -> t_prop -> t_prop val call : t_env -> stmt -> lval option -> kernel_function -> exp list -> diff --git a/src/plugins/wp/tests/wp_acsl/clusters.i b/src/plugins/wp/tests/wp_acsl/clusters.i index c1a2e71653c867c4a4cb93eba854d6f5f55f69fa..a1e3eba197178d084f1726464472e84aecb4a340 100644 --- a/src/plugins/wp/tests/wp_acsl/clusters.i +++ b/src/plugins/wp/tests/wp_acsl/clusters.i @@ -33,7 +33,7 @@ void mutual_1(unsigned n){ // succeeds termination //@ decreases n ; void mutual_2(unsigned n){ - if(n) mutual_1(n-1); + if(n) mutual_1(n-1); // fails decreases: no decreases for mutual_1 simpl_rf(n); // this does not prevent termination proof } @@ -52,7 +52,7 @@ void fp(void (*)(unsigned), unsigned); //@ decreases n ; void function(unsigned n){ - if(n) fp(&function, n-1); + if(n) fp(&function, n-1); // fails decreases: no decreases for fp } // termination fails : recursion without decreases diff --git a/src/plugins/wp/tests/wp_acsl/oracle/clusters.res.oracle b/src/plugins/wp/tests/wp_acsl/oracle/clusters.res.oracle index 22929c54f81d6ef7379d18ccc6ed9f303796e754..61a3435b5bbc033d1871b398ddf626f180645976 100644 --- a/src/plugins/wp/tests/wp_acsl/oracle/clusters.res.oracle +++ b/src/plugins/wp/tests/wp_acsl/oracle/clusters.res.oracle @@ -9,18 +9,12 @@ Missing terminates clause for caller_no_cluster, populates 'terminates \true' [wp] clusters.i:60: Warning: Missing terminates clause for fp, populates 'terminates \true' -[wp] clusters.i:60: Warning: - Unsupported clause @decreases in 'function' (ignored) [wp] clusters.i:54: Warning: Missing terminates clause for function, populates 'terminates \true' [wp] clusters.i:28: Warning: Missing terminates clause for mutual_1, populates 'terminates \true' -[wp] clusters.i:60: Warning: - Unsupported clause @decreases in 'mutual_2' (ignored) [wp] clusters.i:35: Warning: Missing terminates clause for mutual_2, populates 'terminates \true' -[wp] clusters.i:60: Warning: - Unsupported clause @decreases in 'simpl_r' (ignored) [wp] clusters.i:11: Warning: Missing terminates clause for simpl_r, populates 'terminates \true' [wp] clusters.i:15: Warning: @@ -29,8 +23,10 @@ No 'decreases' clause on recursive function 'simpl_rf', cannot prove termination [wp] clusters.i:60: Warning: No 'decreases' clause on recursive function 'mutual_1', cannot prove termination +[wp] clusters.i:36: Warning: No decreases clause for mutual_1 [wp] clusters.i:60: Warning: No 'decreases' clause on recursive function 'fp', cannot prove termination +[wp] clusters.i:55: Warning: No decreases clause for fp ------------------------------------------------------------ Function caller_no_cluster ------------------------------------------------------------ @@ -67,6 +63,16 @@ Prove: true. ------------------------------------------------------------ +Goal Recursion variant: +Call Effect at line 55 +clusters.i:55: warning from wp: + - Warning: Considering non decreasing call, looking for context inconsistency + Reason: No decreases clause for fp +Assume { Type: is_uint32(n). (* Then *) Have: n != 0. } +Prove: false. + +------------------------------------------------------------ + Goal Instance of 'Pre-condition (file clusters.i, line 59) in 'fp'' in 'function' at call 'fp' (file clusters.i, line 55) : Prove: true. @@ -93,6 +99,16 @@ Prove: false. Goal Termination-condition (generated) in 'mutual_2': Prove: true. +------------------------------------------------------------ + +Goal Recursion variant: +Call Effect at line 36 +clusters.i:36: warning from wp: + - Warning: Considering non decreasing call, looking for context inconsistency + Reason: No decreases clause for mutual_1 +Assume { Type: is_uint32(n). (* Then *) Have: n != 0. } +Prove: false. + ------------------------------------------------------------ ------------------------------------------------------------ Function simpl_r @@ -101,6 +117,13 @@ Prove: true. Goal Termination-condition (generated) in 'simpl_r': Prove: true. +------------------------------------------------------------ + +Goal Recursion variant: +Call Effect at line 12 +Assume { Type: is_uint32(n). (* Then *) Have: n != 0. } +Prove: to_uint32(n - 1) < n. + ------------------------------------------------------------ ------------------------------------------------------------ Function simpl_rf diff --git a/src/plugins/wp/tests/wp_acsl/oracle/terminates_formulae.res.oracle b/src/plugins/wp/tests/wp_acsl/oracle/terminates_formulae.res.oracle index cd339be06cf96cd4225b29340256672b0d64ab3b..08ae66d1fc233090df6da03de3c90f2809b26b87 100644 --- a/src/plugins/wp/tests/wp_acsl/oracle/terminates_formulae.res.oracle +++ b/src/plugins/wp/tests/wp_acsl/oracle/terminates_formulae.res.oracle @@ -4,6 +4,8 @@ [wp] Warning: Missing RTE guards [wp] [CFG] Goal general_variant_terminates : Valid (Trivial) [wp] [CFG] Goal variant_terminates : Valid (Trivial) +[wp] tests/wp_acsl/terminates_formulae.i:90: Warning: + No 'decreases' clause on recursive function 'no_decreases', cannot prove termination ------------------------------------------------------------ Function base_call ------------------------------------------------------------ @@ -58,6 +60,36 @@ Goal Termination-condition (file terminates_formulae.i, line 27) in 'call_same': Call Effect at line 29 Prove: true. +------------------------------------------------------------ +------------------------------------------------------------ + Function decreases +------------------------------------------------------------ + +Goal Termination-condition (file tests/wp_acsl/terminates_formulae.i, line 77) in 'decreases': +Prove: true. + +------------------------------------------------------------ + +Goal Recursion variant: +Call Effect at line 80 +Assume { Type: is_uint32(n). (* Goal *) When: P_Q. (* Then *) Have: n != 0. } +Prove: to_uint32(n - 1) < n. + +------------------------------------------------------------ +------------------------------------------------------------ + Function general_decreases +------------------------------------------------------------ + +Goal Termination-condition (file tests/wp_acsl/terminates_formulae.i, line 83) in 'general_decreases': +Prove: true. + +------------------------------------------------------------ + +Goal Recursion variant: +Call Effect at line 86 +Assume { Type: is_uint32(n). (* Goal *) When: P_Q. (* Then *) Have: n != 0. } +Prove: P_Rel(n, to_uint32(n - 1)). + ------------------------------------------------------------ ------------------------------------------------------------ Function general_variant @@ -72,6 +104,20 @@ Goal Follows relation Loop variant at loop (file terminates_formulae.i, line 68) Assume { Type: is_uint32(x). (* Goal *) When: P_Q. (* Then *) Have: 0 < x. } Prove: P_Rel(x, to_uint32(x - 1)). +------------------------------------------------------------ +------------------------------------------------------------ + Function no_decreases +------------------------------------------------------------ + +Goal Termination-condition (file tests/wp_acsl/terminates_formulae.i, line 89) in 'no_decreases' (1/2): +Prove: true. + +------------------------------------------------------------ + +Goal Termination-condition (file tests/wp_acsl/terminates_formulae.i, line 89) in 'no_decreases' (2/2): +Call Result at line 92 +Prove: false. + ------------------------------------------------------------ ------------------------------------------------------------ Function no_variant diff --git a/src/plugins/wp/tests/wp_acsl/oracle_qualif/clusters.res.oracle b/src/plugins/wp/tests/wp_acsl/oracle_qualif/clusters.res.oracle index 8e6f2960697c5548bc3cb90393df4ce92cd713b9..b7b48261bc0d1f853f89e289774ccae1298e46bd 100644 --- a/src/plugins/wp/tests/wp_acsl/oracle_qualif/clusters.res.oracle +++ b/src/plugins/wp/tests/wp_acsl/oracle_qualif/clusters.res.oracle @@ -9,18 +9,12 @@ Missing terminates clause for caller_no_cluster, populates 'terminates \true' [wp] clusters.i:60: Warning: Missing terminates clause for fp, populates 'terminates \true' -[wp] clusters.i:60: Warning: - Unsupported clause @decreases in 'function' (ignored) [wp] clusters.i:54: Warning: Missing terminates clause for function, populates 'terminates \true' [wp] clusters.i:28: Warning: Missing terminates clause for mutual_1, populates 'terminates \true' -[wp] clusters.i:60: Warning: - Unsupported clause @decreases in 'mutual_2' (ignored) [wp] clusters.i:35: Warning: Missing terminates clause for mutual_2, populates 'terminates \true' -[wp] clusters.i:60: Warning: - Unsupported clause @decreases in 'simpl_r' (ignored) [wp] clusters.i:11: Warning: Missing terminates clause for simpl_r, populates 'terminates \true' [wp] clusters.i:15: Warning: @@ -29,31 +23,36 @@ No 'decreases' clause on recursive function 'simpl_rf', cannot prove termination [wp] clusters.i:60: Warning: No 'decreases' clause on recursive function 'mutual_1', cannot prove termination +[wp] clusters.i:36: Warning: No decreases clause for mutual_1 [wp] clusters.i:60: Warning: No 'decreases' clause on recursive function 'fp', cannot prove termination -[wp] 12 goals scheduled +[wp] clusters.i:55: Warning: No decreases clause for fp +[wp] 15 goals scheduled [wp] [Qed] Goal typed_simpl_r_terminates : Valid +[wp] [Alt-Ergo] Goal typed_simpl_r_variant : Valid [wp] [Qed] Goal typed_simpl_rf_terminates_part1 : Valid [wp] [Alt-Ergo] Goal typed_simpl_rf_terminates_part2 : Unsuccess [wp] [Qed] Goal typed_mutual_1_terminates_part1 : Valid [wp] [Alt-Ergo] Goal typed_mutual_1_terminates_part2 : Unsuccess [wp] [Qed] Goal typed_mutual_2_terminates : Valid +[wp] [Alt-Ergo] Goal typed_mutual_2_variant : Unsuccess (Degenerated) [wp] [Qed] Goal typed_caller_no_cluster_terminates : Valid [wp] [Qed] Goal typed_fp_call_point_function_s41 : Valid [wp] [Qed] Goal typed_fp_terminates_part1 : Valid [wp] [Alt-Ergo] Goal typed_fp_terminates_part2 : Unsuccess [wp] [Qed] Goal typed_function_terminates : Valid +[wp] [Alt-Ergo] Goal typed_function_variant : Unsuccess (Degenerated) [wp] [Qed] Goal typed_function_call_fp_requires : Valid -[wp] Proved goals: 10 / 13 +[wp] Proved goals: 11 / 16 Qed: 9 - Alt-Ergo: 0 (unsuccess: 3) + Alt-Ergo: 1 (unsuccess: 5) ------------------------------------------------------------ Functions WP Alt-Ergo Total Success - simpl_r 1 - 1 100% + simpl_r 1 1 2 100% simpl_rf 1 - 2 50.0% mutual_1 1 - 2 50.0% - mutual_2 1 - 1 100% + mutual_2 1 - 2 50.0% caller_no_cluster 1 - 1 100% fp 2 - 3 66.7% - function 2 - 2 100% + function 2 - 3 66.7% ------------------------------------------------------------ diff --git a/src/plugins/wp/tests/wp_acsl/oracle_qualif/terminates_formulae.res.oracle b/src/plugins/wp/tests/wp_acsl/oracle_qualif/terminates_formulae.res.oracle index 2beb6a738f11a1c2da19a7d9151bf108dabc747a..61f36ae2e4f9b9c7838a21b3561a4821e4641d59 100644 --- a/src/plugins/wp/tests/wp_acsl/oracle_qualif/terminates_formulae.res.oracle +++ b/src/plugins/wp/tests/wp_acsl/oracle_qualif/terminates_formulae.res.oracle @@ -4,7 +4,9 @@ [wp] Warning: Missing RTE guards [wp] [CFG] Goal general_variant_terminates : Valid (Trivial) [wp] [CFG] Goal variant_terminates : Valid (Trivial) -[wp] 12 goals scheduled +[wp] tests/wp_acsl/terminates_formulae.i:90: Warning: + No 'decreases' clause on recursive function 'no_decreases', cannot prove termination +[wp] 18 goals scheduled [wp] [Alt-Ergo] Goal typed_base_call_terminates : Unsuccess [wp] [Qed] Goal typed_call_same_terminates : Valid [wp] [Alt-Ergo] Goal typed_call_change_terminates : Unsuccess @@ -17,9 +19,15 @@ [wp] [Alt-Ergo] Goal typed_general_variant_loop_variant_relation : Valid [wp] [Alt-Ergo] Goal typed_no_variant_terminates : Unsuccess [wp] [Qed] Goal typed_no_variant_loop_assigns : Valid -[wp] Proved goals: 10 / 14 - Qed: 6 - Alt-Ergo: 2 (unsuccess: 4) +[wp] [Qed] Goal typed_decreases_terminates : Valid +[wp] [Alt-Ergo] Goal typed_decreases_variant : Valid +[wp] [Qed] Goal typed_general_decreases_terminates : Valid +[wp] [Alt-Ergo] Goal typed_general_decreases_variant : Valid +[wp] [Qed] Goal typed_no_decreases_terminates_part1 : Valid +[wp] [Alt-Ergo] Goal typed_no_decreases_terminates_part2 : Unsuccess +[wp] Proved goals: 15 / 20 + Qed: 9 + Alt-Ergo: 4 (unsuccess: 5) ------------------------------------------------------------ Functions WP Alt-Ergo Total Success base_call - - 1 0.0% @@ -30,4 +38,7 @@ variant 2 1 3 100% general_variant 1 1 2 100% no_variant 1 - 2 50.0% + decreases 1 1 2 100% + general_decreases 1 1 2 100% + no_decreases 1 - 2 50.0% ------------------------------------------------------------ diff --git a/src/plugins/wp/tests/wp_acsl/terminates_formulae.i b/src/plugins/wp/tests/wp_acsl/terminates_formulae.i index 9cf88ab44eda973fe845a63f1563ee8f3d62623c..5a3e16c8827f5acab07ea3435f02d328c7c12c3d 100644 --- a/src/plugins/wp/tests/wp_acsl/terminates_formulae.i +++ b/src/plugins/wp/tests/wp_acsl/terminates_formulae.i @@ -73,3 +73,20 @@ void no_variant(void){ //@ loop assigns i ; for(unsigned i = 3; i > 0; --i); } + +/*@ terminates Q ; + decreases n ; */ +void decreases(unsigned n){ + if(n != 0) decreases(n-1) ; +} + +/*@ terminates Q ; + decreases n for Rel; */ +void general_decreases(unsigned n){ + if(n != 0) general_decreases(n-1) ; +} + +/*@ terminates Q ; */ +void no_decreases(unsigned n){ + if(n != 0) no_decreases(n-1) ; +} diff --git a/src/plugins/wp/wpPropId.ml b/src/plugins/wp/wpPropId.ml index 9da760aa83318e9b45a3a48faee66efe331cf235..8071f17896070904334e96daeee1a7036b250bc4 100644 --- a/src/plugins/wp/wpPropId.ml +++ b/src/plugins/wp/wpPropId.ml @@ -46,7 +46,8 @@ type prop_kind = | PKAFctOut (** computation related to the function assigns on normal termination *) | PKAFctExit (** computation related to the function assigns on exit termination *) | PKTerminates (** computation related to the termination *) - | PKSmoke (** expected to fail *) + | PKDecreases (** computation related to the decreases clause *) + | PKSmoke (** expected to fail *) | PKPre of kernel_function * stmt * Property.t (** precondition for function at stmt, property of the require. Many information that should come @@ -75,7 +76,6 @@ let tactical ~gid = (* --- Category --- *) (* -------------------------------------------------------------------------- *) -let kind_of_id p = p.p_kind let parts_of_id p = p.p_part let property_of_id p = p.p_prop let doomed_if_valid p = p.p_doomed @@ -153,7 +153,7 @@ let mk_disj_bhv_id (kf,ki,active,disj) = let mk_compl_bhv_id (kf,ki,active,comp) = mk_prop PKProp (Property.ip_of_complete kf ki active comp) let mk_decrease_id kf s x = - mk_prop PKProp (Property.ip_of_decreases kf s x) + mk_prop PKDecreases (Property.ip_of_decreases kf s x) let mk_lemma_id l = mk_prop PKProp (LogicUsage.ip_lemma l) @@ -222,6 +222,7 @@ let kind_order = function | PKTactic -> 11 | PKSmoke -> 12 | PKTerminates -> 13 + | PKDecreases -> 14 let compare_kind k1 k2 = match k1, k2 with PKPre (kf1, ki1, p1), PKPre (kf2, ki2, p2) -> @@ -336,8 +337,6 @@ end = struct let basename_of_prop_id p = match p.p_kind , p.p_prop with - | (PKTactic | PKCheck | PKProp | PKPropLoop | PKSmoke | PKTerminates) , p -> - base_id_prop_txt p | PKEstablished , p -> base_id_prop_txt p ^ "_established" | PKPreserved , p -> base_id_prop_txt p ^ "_preserved" | PKVarDecr , p -> base_id_prop_txt p ^ "_decrease" @@ -350,6 +349,9 @@ end = struct Kernel_function.get_name (Kernel_function.find_englobing_kf stmt) in Printf.sprintf "%s_call_%s" kf_name_of_stmt (base_id_prop_txt pre) + | _ , p -> + base_id_prop_txt p + (** function used to normalize basename *) let normalize_basename s = @@ -420,8 +422,6 @@ struct let get_prop_id_base p = match p.p_kind , p.p_prop with - | (PKTactic | PKCheck | PKProp | PKPropLoop | PKSmoke | PKTerminates) , p -> - get_ip p | PKEstablished , p -> get_ip p ^ "_established" | PKPreserved , p -> get_ip p ^ "_preserved" | PKVarDecr , p -> get_ip p ^ "_decrease" @@ -443,6 +443,9 @@ struct ip_string) in call_string^"_"^ip_string + | _ , p -> + get_ip p + let get_prop_id_basename p = let basename = get_prop_id_base p in @@ -576,6 +579,7 @@ let label_of_kind = function | PKAFctOut -> "Function assigns" | PKAFctExit -> "Exit assigns" | PKTerminates -> "Terminates" + | PKDecreases -> "Decreases" | PKPre(kf,_,_) -> Printf.sprintf "Precondition for '%s'" (Kernel_function.get_name kf) @@ -592,7 +596,8 @@ struct | None -> () | Some(k,n) -> fprintf fmt " (%d/%d)" (succ k) n let pp_subprop fmt p = match p.p_kind with - | PKProp | PKTactic | PKCheck | PKPropLoop | PKSmoke | PKTerminates -> () + | PKProp | PKTactic | PKCheck | PKPropLoop | PKSmoke + | PKTerminates | PKDecreases -> () | PKEstablished -> pp_print_string fmt " (established)" | PKPreserved -> pp_print_string fmt " (preserved)" | PKVarDecr -> pp_print_string fmt " (decrease)" @@ -673,6 +678,7 @@ let propid_hints hs p = | PKAFctOut , _ -> add_required hs "return" | PKAFctExit , _ -> add_required hs "exit" | PKTerminates , _ -> add_required hs "terminates" + | PKDecreases , _ -> add_required hs "decreases" | PKPre(kf,st,_) , _ -> add_required hs ("precond-" ^ Kernel_function.get_name kf) ; stmt_hints hs st @@ -747,7 +753,7 @@ let prop_id_keys p = let pp_goal_kind fmt = function | PKTactic | PKSmoke | PKCheck - | PKProp | PKPropLoop | PKAFctOut | PKAFctExit | PKTerminates + | PKProp | PKPropLoop | PKAFctOut | PKAFctExit | PKTerminates | PKDecreases | PKPre _ -> () | PKEstablished -> Format.pp_print_string fmt "Establishment of " | PKPreserved -> Format.pp_print_string fmt "Preservation of " @@ -1059,7 +1065,7 @@ let split_map f pid gs = let subproofs id = match id.p_kind with | PKCheck -> 0 | PKProp | PKSmoke | PKTactic | PKPre _ | PKPropLoop - | PKTerminates | PKVarRel -> 1 + | PKTerminates | PKDecreases | PKVarRel -> 1 | PKEstablished | PKPreserved | PKVarDecr | PKVarPos | PKAFctExit | PKAFctOut -> 2 @@ -1067,7 +1073,7 @@ let subproofs id = match id.p_kind with let subproof_idx id = match id.p_kind with | PKCheck -> (-1) (* 0/0 *) | PKProp | PKTactic | PKPre _ | PKSmoke | PKPropLoop - | PKTerminates | PKVarRel -> 0 (* 1/1 *) + | PKTerminates | PKDecreases | PKVarRel -> 0 (* 1/1 *) | PKPreserved -> 0 (* 1/2 *) | PKEstablished-> 1 (* 2/2 *) | PKVarDecr -> 0 (* 1/2 *) @@ -1117,7 +1123,8 @@ let get_induction p = | IPAssigns {ias_kf; ias_kinstr=Kstmt stmt} -> Some (ias_kf, stmt) | _ -> None in match p.p_kind with - | PKCheck|PKSmoke |PKAFctOut|PKAFctExit|PKPre _ | PKTactic| PKTerminates -> + | PKCheck | PKSmoke | PKAFctOut | PKAFctExit | PKPre _ + | PKTactic | PKTerminates | PKDecreases -> None | PKProp -> let loop_stmt_opt = match get_stmt (property_of_id p) with diff --git a/src/plugins/wp/wpPropId.mli b/src/plugins/wp/wpPropId.mli index 559492ad8374b347e8226ecbca02b56a2cd1e674..469a8ee011df1accd813977b937c03d755ac237f 100644 --- a/src/plugins/wp/wpPropId.mli +++ b/src/plugins/wp/wpPropId.mli @@ -89,24 +89,6 @@ val are_selected_names: string list -> string list -> bool (** [are_selected_names asked names] checks if [names] of a property are selected according to [asked] names. *) -type prop_kind = - | PKTactic (** tactical sub-goal *) - | PKCheck (** internal check *) - | PKProp (** normal property *) - | PKEstablished (** computation related to a loop property before the loop. *) - | PKPreserved (** computation related to a loop property inside the loop. *) - | PKPropLoop (** loop property used as hypothesis inside a loop. *) - | PKVarDecr (** computation related to the decreasing of a variant in a loop *) - | PKVarPos (** computation related to a loop variant being positive *) - | PKVarRel (** computation related to a generalized loop variant *) - | PKAFctOut (** computation related to the function assigns on normal termination *) - | PKAFctExit (** computation related to the function assigns on exit termination *) - | PKTerminates (** computation related to the termination *) - | PKSmoke (** smoke property *) - | PKPre of kernel_function * stmt * Property.t (** precondition for function - at stmt, property of the require. Many information that should come - from the p_prop part of the prop_id, but in the PKPre case, - it seems that it is hidden in a IPBlob property ! *) val pretty : Format.formatter -> prop_id -> unit val pretty_context : Description.kf -> Format.formatter -> prop_id -> unit @@ -285,9 +267,6 @@ val split_map : (prop_id -> 'a -> 'b) -> prop_id -> 'a list -> 'b list (** [mk_part pid (k, n)] build the identification for the [k/n] part of [pid].*) val mk_part : prop_id -> (int * int) -> prop_id -(** get the 'kind' information. *) -val kind_of_id : prop_id -> prop_kind - (** get the 'part' information. *) val parts_of_id : prop_id -> (int * int) option