diff --git a/src/plugins/value/engine/compute_functions.ml b/src/plugins/value/engine/compute_functions.ml index 6a721abfab960aee57ecf241fbf28041f6e335cd..8eaabed066410ff3309ad58a961f42d4faafd31f 100644 --- a/src/plugins/value/engine/compute_functions.ml +++ b/src/plugins/value/engine/compute_functions.ml @@ -154,6 +154,14 @@ module Make (Abstract: Abstractions.Eva) = struct | None -> fun _ -> assert false | Some get -> fun location -> get location + let unroll_recursive_call kf = + let call_stack = Value_util.call_stack () in + let previous_calls = List.filter (fun (f, _) -> f == kf) call_stack in + let depth = List.length previous_calls in + assert (depth > 0); + let limit = Value_parameters.RecursiveUnroll.get () in + depth <= limit + (* Compute a call to [kf] in the state [state]. The evaluation will be done either using the body of [kf] or its specification, depending on whether the body exists and on option [-eva-use-spec]. [call_kinstr] @@ -172,8 +180,8 @@ module Make (Abstract: Abstractions.Eva) = struct Value_types.Callstack.pretty_short call_stack Cil_datatype.Location.pretty (Cil_datatype.Kinstr.loc call_kinstr); let use_spec = - if call.recursive then - `Spec (Recursion.empty_spec_for_recursive_call kf) + if call.recursive && not (unroll_recursive_call kf) then + `Spec (Recursion.recursive_spec kf) else match kf.fundec with | Declaration (_,_,_,_) -> `Spec (Annotations.funspec kf) diff --git a/src/plugins/value/engine/recursion.ml b/src/plugins/value/engine/recursion.ml index af9cefc4579e3cdb435eec9429d30581c92236f6..83133b6a202d3be19b18d13144971ec5c664b475 100644 --- a/src/plugins/value/engine/recursion.ml +++ b/src/plugins/value/engine/recursion.ml @@ -37,18 +37,29 @@ let check_formals_non_referenced kf = whose address is taken. Analysis may be unsound." Kernel_function.pretty kf -let warn_recursive_call kf call_stack = +let warn_recursive_call kf = Value_parameters.feedback ~once:true ~current:true - "@[@[detected@ recursive@ call@ (%a <- %a)@]@;@]" - Kernel_function.pretty kf Value_types.Callstack.pretty call_stack; + "@[detected recursive call@ of function %a.@]" + Kernel_function.pretty kf; check_formals_non_referenced kf +let recursive_spec kf = + let funspec = Annotations.funspec ~populate:false kf in + if Cil.is_empty_funspec funspec then + Value_parameters.abort ~current:true + "@[Recursive call to %a@ without a specification.@ Try to increase@ \ + the %s parameter@ or write a specification@ for function %a.@]" + Kernel_function.pretty kf + Value_parameters.RecursiveUnroll.name + Kernel_function.pretty kf + else funspec + (* Check whether the function at the top of the call-stack starts a recursive call. *) let is_recursive_call kf = let call_stack = Value_util.call_stack () in if List.exists (fun (f, _) -> f == kf) call_stack - then (warn_recursive_call kf call_stack; true) + then (warn_recursive_call kf; true) else false (* Find a spec for a function [kf] that begins a recursive call. If [kf] @@ -74,7 +85,7 @@ let _spec_for_recursive_call kf = ~silent_about_merging_behav:true spec initial_spec; spec -let empty_spec_for_recursive_call kf = +let _empty_spec_for_recursive_call kf = let typ_res = Kernel_function.get_return_type kf in let empty = Cil.empty_funspec () in let assigns = diff --git a/src/plugins/value/engine/recursion.mli b/src/plugins/value/engine/recursion.mli index 80c28c1cd88641aa45a2fe58c1f20d139e1cb9a4..a446573aca8c6858c701f14993c1beef7baaae3f 100644 --- a/src/plugins/value/engine/recursion.mli +++ b/src/plugins/value/engine/recursion.mli @@ -24,15 +24,12 @@ open Cil_types +val recursive_spec: kernel_function -> funspec + val is_recursive_call: kernel_function -> bool (** Given the current state of the call stack, detect whether the given given function would start a recursive cycle. *) -val empty_spec_for_recursive_call: kernel_function -> spec -(** Generate an empty spec [assigns \nothing] or - [assigns \result \from \nothing], to be used to "approximate" the - results of a recursive call. *) - (** TODO *) val make_recursive_call: kernel_function -> Eval.recursion diff --git a/src/plugins/value/engine/transfer_stmt.ml b/src/plugins/value/engine/transfer_stmt.ml index 853705200e9832fcaa6a9fef87253f1784be3b08..8580e99fc2d08ea9d98ecea0600adbdf91adda72 100644 --- a/src/plugins/value/engine/transfer_stmt.ml +++ b/src/plugins/value/engine/transfer_stmt.ml @@ -472,22 +472,15 @@ module Make (Abstract: Abstractions.Eva) = struct >>- fun reductions -> (* The formals (and the locals) of the called function leave scope. *) let post = Domain.leave_scope kf_callee leaving_vars state in + let recursion = Option.map Recursion.revert_recursion recursion in (* Computes the state after the call, from the post state at the end of the called function, and the pre state at the call site. *) Domain.finalize_call stmt call recursion ~pre ~post >>- fun state -> (* Backward propagates the [reductions] on the concrete arguments. *) reduce_arguments reductions state >>- fun state -> treat_return ~kf_callee lv call.return stmt state - and process_recursive state = - (* When the call is recursive, formals have not been added to the - domains. Do not reduce them, and more importantly, do not remove - them from the scope. (Because the instance from the initial, - non-recursive, call are still present.) *) - Domain.finalize_call stmt call recursion ~pre ~post:state >>- fun state -> - treat_return ~kf_callee lv call.return stmt state in let states = - let process = if call.recursive then process_recursive else process in List.fold_left (fun acc return -> Bottom.add_to_list (process return) acc) [] result @@ -536,25 +529,17 @@ module Make (Abstract: Abstractions.Eva) = struct let recursive = Recursion.is_recursive_call kf in let return = Library_functions.get_retres_vi kf in let arguments, rest = - if recursive then - (* For recursive calls, we evaluate 'assigns \result \from \nothing' - using a specification. We generate a dummy [call] object in which - formals are not present. This way, domains will not overwrite - the formals of the recursive function (which would be present - in scope twice). *) - [], [] - else - let formals = Kernel_function.get_formals kf in - let rec format_arguments acc args formals = match args, formals with - | _, [] -> acc, args - | [], _ -> assert false - | (concrete, avalue) :: args, formal :: formals -> - let argument = { formal ; concrete; avalue } in - format_arguments (argument :: acc) args formals - in - let arguments, rest = format_arguments [] args formals in - let arguments = List.rev arguments in - arguments, rest + let formals = Kernel_function.get_formals kf in + let rec format_arguments acc args formals = match args, formals with + | _, [] -> acc, args + | [], _ -> assert false + | (concrete, avalue) :: args, formal :: formals -> + let argument = { formal ; concrete; avalue } in + format_arguments (argument :: acc) args formals + in + let arguments, rest = format_arguments [] args formals in + let arguments = List.rev arguments in + arguments, rest in {kf; arguments; rest; return; recursive; }