-
Thibault Martin authoredThibault Martin authored
populate_spec.ml 32.68 KiB
(**************************************************************************)
(* *)
(* This file is part of Frama-C. *)
(* *)
(* Copyright (C) 2007-2023 *)
(* CEA (Commissariat à l'énergie atomique et aux énergies *)
(* 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 mode =
(* Modes available for specification generation. Skip is only available via
-generated-spec-custom (cf. option description). *)
| ACSL | Safe | Frama_C | Skip
(* Allow user to use a custom mode, see {!register}. *)
| Other of string
(* Allow customization, each clause can be handled with a different {!mode}. *)
type config = {
c_exits: mode;
c_assigns: mode;
c_requires: mode;
c_allocates: mode;
c_terminates: mode;
}
(* Either keep old specification or generate a new one. Existing specification
from complete behaviors can be combined and used for default behavior. *)
type 'a result = Kept | Generated of 'a
type t_exits = (termination_kind * identified_predicate) list
type t_assigns = Cil_types.assigns
type t_requires = identified_predicate list
type t_allocates = Cil_types.allocation
type t_terminates = identified_predicate option
(* Generation function type and status. *)
type 'a gen = (kernel_function -> spec -> 'a)
type status = Property_status.emitted_status
(* For each clause, we need a generation function and a status to be emitted. *)
type 'a elem = {
gen: 'a gen option;
status : status option;
}
(* Allow user to create a mode by choosing how each clause should be generated
and which status to emit. *)
type custom_mode = {
custom_exits: t_exits elem;
custom_assigns: t_assigns elem;
custom_requires: t_requires elem;
custom_allocates: t_allocates elem;
custom_terminates: t_terminates elem;
}
(* Used to store custom modes. *)
let custom_modes = Hashtbl.create 17
let default = Cil.default_behavior_name
let emitter_populate =
Emitter.create "Populated spec"
[ Funspec ] ~correctness:[] ~tuning:[]
let emitter_status =
Emitter.create "Populated status"
[ Property_status ] ~correctness:[] ~tuning:[]
(* Emit [status] on the property [ppt]. *)
let emit_status status ppt =
Property_status.emit emitter_status ~hyps:[] ppt status
(* Generic function to fill the clause hashtbl.
[filter] returns all the clauses of behavior [b] and depend on the type of
clause. [is_empty] correspond to each {!Generator.is_empty} function.
The table also remembers for each behavior if it has assumes clause or not.
*)
let collect_behaviors_generic filter is_empty spec =
let table = Hashtbl.create 17 in
let iter b =
let clauses = filter b in
if not (is_empty clauses) then
Hashtbl.add table b.b_name (b.b_assumes = [], clauses)
in
List.iter iter spec.spec_behavior;
table
(* Filter [table] to keep behaviors without guard (assume) clause. *)
let unguarded table =
let filter _ (unguarded, clause) acc =
if unguarded then clause :: acc else acc
in
match Hashtbl.fold filter table [] with
| [] -> None
| l -> Some l
(* Find the first group of complete behaviors in which all behaviors are in our
table. *)
let completes (type clause) completes table =
let exception Ok of clause list in
let collect l b =
let _, c = Hashtbl.find table b in
c :: l
in
let collect bhvs =
try let r = List.fold_left collect [] bhvs in raise (Ok r)
with Not_found -> ()
in
try
List.iter collect completes; None
with Ok l -> Some l
(* Concat all clauses from the table into a list. *)
let get_all_clauses table =
let clauses =
Hashtbl.fold (fun _ (_,clause) clauses -> clause :: clauses) table []
in
match clauses with
| [] -> None
| l -> Some l
(* This function search for existing clauses inside our table. The search is
done in a specific order and stops when some clauses are found :
- unguarded behaviors (no assumes).
- complete behaviors (each one of them needs to be in our table).
- all behaviors in our table.
It is a generic function used by {!Generator.get_clauses}.
*)
let get_clauses_generic spec table =
match unguarded table with
| Some l -> Some l
| None ->
match completes spec.spec_complete_behaviors table with
| Some l -> Some l
| None -> get_all_clauses table
(* Register a new custom mode (or replace an existing one). *)
let register ?gen_exits ?status_exits ?gen_assigns ?status_assigns
?gen_requires ?gen_allocates ?status_allocates ?gen_terminates
?status_terminates name =
let f gen status = {gen; status} in
let mode = {
custom_exits = f gen_exits status_exits;
custom_assigns = f gen_assigns status_assigns;
custom_requires = f gen_requires None;
custom_allocates = f gen_allocates status_allocates;
custom_terminates = f gen_terminates status_terminates;
} in
Hashtbl.replace custom_modes name mode
(* Return a custom mode from the registered ones if it exists. *)
let get_custom_mode mode =
match Hashtbl.find_opt custom_modes mode with
| None -> Kernel.abort "Mode %s is not registered" mode
| Some custom_mode -> custom_mode
(* Use this instead of Identified_term.compare. *)
let compare_it it1 it2 =
Cil_datatype.Term.compare it1.it_content it2.it_content
(* Return true if [kf] is a builtin of Frama-C. *)
let is_frama_c_builtin kf =
let v = Kernel_function.get_vi kf in
Cil_builtins.is_builtin v || Cil_builtins.is_special_builtin v.vname
(* This module is used to define clauses generators. *)
module type Generator =
sig
(* Generator's clause : exits, assigns, requires, allocates or terminates. *)
type clause
(* Store informations regarding original specification clauses. *)
type behaviors
(* Used for messages in logs/warnings, etc. *)
val name : string
(* Used to check if we actually generated something. *)
val is_empty : clause -> bool
(* Return true if default behavior contains this Generator's clause. *)
val has_default_behavior : behaviors -> bool
(* Collect all clauses from function specification. *)
val collect_behaviors : spec -> behaviors
(* Collect clauses given a [spec] and the result of {!collect_behaviors}. *)
val get_clauses : spec -> behaviors -> clause list option
(* Generate a default clause in ACSL mode. *)
val acsl_default : unit -> clause
(* Generate a default clause in Safe mode. *)
val safe_default : kernel_function -> clause
(* Generate a default clause in Frama_C mode. *)
val frama_c_default : kernel_function -> clause
(* Generate a default clause in Other (custom) modes. *)
val custom_default : string -> kernel_function -> spec -> clause
(* Combine clauses from existing behaviors to generate clauses inside
default behavior. *)
val combine_default : clause list -> clause
(* Emit property status depending on the selected mode. *)
val emit : mode -> kernel_function -> funbehavior -> clause -> unit
end
(* Build Generators. *)
module Make(G : Generator) =
struct
(* Either combine existing clauses or generate new ones depending on the
selected mode and original specification. *)
let combine_or_default mode kf spec table =
if mode = ACSL then false, G.acsl_default ()
else
let clauses_opt = G.get_clauses spec table in
match mode, clauses_opt with
| (Safe | Frama_C | Other _), Some clauses ->
true, G.combine_default clauses
| Safe, None ->
false, G.safe_default kf
| Frama_C, None ->
false, G.frama_c_default kf
| Other mode, None ->
false, G.custom_default mode kf spec
| (Skip | ACSL), _ -> assert false
(* Return a new clause as [Generated g] or [Kept] (if no action is needed),
and an option used for warnings. [combined] is true if the generated
clause comes from existing ones.
*)
let get_default mode kf spec =
let table = G.collect_behaviors spec in
if mode = Skip || G.has_default_behavior table then Kept, None
else
let combined, g = combine_or_default mode kf spec table in
let has_body = Kernel_function.has_definition kf in
if G.is_empty g then Kept, None
else if has_body || is_frama_c_builtin kf then Generated g, None
else Generated g, Some(combined, G.name)
(* Interface to call {!G.emit}. Only emit properties for non empty clauses
generated for a declaration. *)
let emit mode kf bhv = function
| Kept -> ()
| Generated _ when Kernel_function.has_definition kf -> ()
| Generated clauses ->
G.emit mode kf bhv clauses
end
(*******************************************************************)
(* *********************** Exits generator *********************** *)
(* |-------------------------------------------------------------| *)
(* | ACSL | Frama-c | Safe | Other | *)
(* |-------|-------|-------|------|-------|-------|-------|------| *)
(* | Proto | Body | Proto | Body | Proto | Body | Proto | Body | *)
(* |-------|-------|-------|------|-------|-------|-------|------| *)
(* | false | false | ACSL | ACSL | ----- | false | ??? | ?? | *)
(* |-------------------------------------------------------------| *)
(* *****************************************************************)
(* ****** Status emitted on prototypes ******)
(* |--------------------------------------| *)
(* | ACSL | Frama-c | Safe | Other | *)
(* |------|-----------|-----------|-------| *)
(* | True | Dont_know | Dont_know | ??? | *)
(* |--------------------------------------| *)
(********************************************)
module Exits_generator =
struct
type clause = t_exits
type behaviors = (string, (bool * clause)) Hashtbl.t
let name = "exits"
let is_empty c = c = []
let has_default_behavior behaviors =
Hashtbl.mem behaviors default
let collect_behaviors spec =
let filter b = List.filter (fun (k, _ ) -> Exits = k) b.b_post_cond in
collect_behaviors_generic filter is_empty spec
let get_clauses = get_clauses_generic
let acsl_default () =
[ Exits, Logic_const.(new_predicate pfalse) ]
let safe_default kf =
if Kernel_function.has_definition kf
then [ Exits, Logic_const.(new_predicate pfalse) ]
else []
let frama_c_default _kf =
acsl_default ()
let combine_default (clauses : clause list) =
let collect acc clauses = List.rev_append (List.rev clauses) acc in
let preds =
List.map
(fun p -> p.ip_content.tp_statement)
(List.fold_left collect [] clauses |> List.split |> snd)
|> List.sort_uniq (Cil_datatype.PredicateStructEq.compare)
in
[ Exits, Logic_const.new_predicate (Logic_const.pors preds) ]
let custom_default mode kf spec =
let custom_mode = get_custom_mode mode in
match custom_mode.custom_exits.gen with
| None ->
Kernel.warning ~once:true
"Custom generation from mode %s not defined for exits, using \
frama-c mode instead" mode;
frama_c_default kf
| Some f -> f kf spec
let emit_status kf bhv exits status =
let ppt_l =
List.map (fun e -> Property.ip_of_ensures kf Kglobal bhv e) exits
in
List.iter (emit_status status) ppt_l
let emit mode kf bhv exits =
match mode with
| Skip -> assert false
| ACSL | Safe | Frama_C ->
emit_status kf bhv exits Property_status.Dont_know
| Other mode ->
let custom_mode = get_custom_mode mode in
match custom_mode.custom_exits.status with
| None ->
Kernel.warning ~once:true
"Custom status from mode %s not defined for exits" mode;
()
| Some pst -> emit_status kf bhv exits pst
end
(*********************************************************************)
(* *********************** Assigns generator *********************** *)
(* |---------------------------------------------------------------| *)
(* | ACSL | Frama-c | Safe | Other | *)
(* |-------|-------|-------|------|-------|---------|-------|------| *)
(* | Proto | Body | Proto | Body | Proto | Body | Proto | Body | *)
(* |-------|-------|-------|------|-------|---------|-------|------| *)
(* | Any | Any | Auto | ACSL | Any | Nothing | ??? | ?? | *)
(* |---------------------------------------------------------------| *)
(* *******************************************************************)
(* ****** Status emitted on prototypes ******)
(* |--------------------------------------| *)
(* | ACSL | Frama-c | Safe | Other | *)
(* |------|-----------|-----------|-------| *)
(* | ---- | Dont_know | Dont_know | ??? | *)
(* |--------------------------------------| *)
(********************************************)
module Assigns_generator =
struct
type clause = t_assigns
type behaviors = (string, (bool * clause)) Hashtbl.t
let name = "assigns"
let is_empty c = c = WritesAny
let has_default_behavior behaviors =
Hashtbl.mem behaviors default
let collect_behaviors spec =
let filter b = b.b_assigns in
collect_behaviors_generic filter is_empty spec
let get_clauses = get_clauses_generic
let acsl_default () =
WritesAny
let safe_default kf =
if Kernel_function.has_definition kf
then Writes []
else WritesAny
let frama_c_default kf =
(* TODO: use genassigns for Definitions. *)
Writes (Infer_assigns.from_prototype kf)
let compare_deps d1 d2 =
match d1, d2 with
| FromAny, FromAny -> 0
| FromAny, From _ -> 1
| From _, FromAny -> -1
| From l1, From l2 ->
Extlib.list_compare compare_it l1 l2
let compare_from (f1, d1) (f2, d2) =
let r = compare_it f1 f2 in
if r <> 0 then r else compare_deps d1 d2
let combine_default clauses =
(* Note: combination is made on a set of behaviors in the sens of
{!get_clauses} and {!collect_behaviors}, thus here all clauses are
[Writes ...]. *)
let collect acc = function
| Writes l -> List.rev_append (List.rev l) acc
| WritesAny -> assert false
in
let deps = function
| FromAny -> FromAny
| From l -> From (List.sort_uniq compare_it l)
in
let froms =
List.fold_left collect [] clauses
|> List.map (fun (e, ds) -> e, deps ds)
in
Writes (List.sort_uniq compare_from froms)
let custom_default mode kf spec =
let custom_mode = get_custom_mode mode in
match custom_mode.custom_assigns.gen with
| None ->
Kernel.warning ~once:true
"Custom generation from mode %s not defined for assigns, using \
frama-c mode instead" mode;
frama_c_default kf
| Some f -> f kf spec
let emit_status kf bhv assigns status =
let ppt_opt =
Property.ip_of_assigns kf Kglobal
(Property.Id_contract (Datatype.String.Set.empty,bhv)) assigns
in
Option.iter (emit_status status) ppt_opt;
match assigns with
| WritesAny -> assert false
| Writes froms ->
let emit from =
let ppt_opt =
Property.ip_of_from
kf Kglobal
(Property.Id_contract (Datatype.String.Set.empty,bhv)) from
in
Option.iter (emit_status status) ppt_opt
in
List.iter emit froms
let emit mode kf bhv assigns =
match mode with
| Skip | ACSL -> assert false
| Safe | Frama_C -> emit_status kf bhv assigns Property_status.Dont_know
| Other mode ->
let custom_mode = get_custom_mode mode in
match custom_mode.custom_assigns.status with
| None ->
Kernel.warning ~once:true
"Custom status from mode %s not defined for assigns" mode;
()
| Some pst -> emit_status kf bhv assigns pst
end
(*****************************************************************)
(* ********************* Requires generator ******************** *)
(* |-----------------------------------------------------------| *)
(* | ACSL | Frama-c | Safe | Other | *)
(* |-------|------|-------|------|-------|------|-------|------| *)
(* | Proto | Body | Proto | Body | Proto | Body | Proto | Body | *)
(* |-------|------|-------|------|-------|------|-------|------| *)
(* | ----- | ---- | ----- | ---- | false | ---- | ??? | ?? | *)
(* |-----------------------------------------------------------| *)
(* ***************************************************************)
(* ** Status emitted on prototypes ***)
(* |-------------------------------| *)
(* | ACSL | Frama-c | Safe | Other | *)
(* |------|---------|------|-------| *)
(* | ---- | ------- | ---- | ----- | *)
(* |-------------------------------| *)
(*************************************)
module Requires_generator =
struct
type clause = t_requires
type behaviors = (string, (bool * clause)) Hashtbl.t
let name = "requires"
let is_empty c = c = []
let has_default_behavior behaviors =
Hashtbl.mem behaviors default
let collect_behaviors spec =
let filter b = b.b_requires in
collect_behaviors_generic filter is_empty spec
let get_clauses = get_clauses_generic
let acsl_default () = []
let safe_default kf =
if Kernel_function.has_definition kf
then []
else [ Logic_const.(new_predicate pfalse) ]
let frama_c_default _kf =
acsl_default ()
let combine_default (clauses : clause list) =
let flatten_require clause =
List.map (fun p -> p.ip_content.tp_statement) clause
|> List.sort_uniq Cil_datatype.PredicateStructEq.compare
|> Logic_const.pands
in
let preds =
List.map flatten_require clauses
|> Logic_const.pors
in
[ Logic_const.new_predicate preds ]
let custom_default mode kf spec =
let custom_mode = get_custom_mode mode in
match custom_mode.custom_requires.gen with
| None ->
Kernel.warning ~once:true
"Custom generation from mode %s not defined for requires, using \
frama-c mode instead" mode;
frama_c_default kf
| Some f -> f kf spec
let emit _mode _kf _bhv _requires = ()
end
(*************************************************************************)
(* ************************* Allocates generator *********************** *)
(* |-------------------------------------------------------------------| *)
(* | ACSL | Frama-c | Safe | Other | *)
(* |---------|---------|-------|------|-------|---------|-------|------| *)
(* | Proto | Body | Proto | Body | Proto | Body | Proto | Body | *)
(* |---------|---------|-------|------|-------|---------|-------|------| *)
(* | Nothing | Nothing | ACSL | ACSL | Any | Nothing | ??? | ?? | *)
(* |-------------------------------------------------------------------| *)
(* ***********************************************************************)
(* ****** Status emitted on prototypes ******)
(* |--------------------------------------| *)
(* | ACSL | Frama-c | Safe | Other | *)
(* |------|-----------|-----------|-------| *)
(* | True | Dont_know | Dont_know | ??? | *)
(* |--------------------------------------| *)
(********************************************)
module Allocates_generator =
struct
type clause = t_allocates
type behaviors = (string, (bool * clause)) Hashtbl.t
let name = "allocates"
let is_empty c = c = FreeAllocAny
let has_default_behavior behaviors =
Hashtbl.mem behaviors default
let collect_behaviors spec =
let filter b = b.b_allocation in
collect_behaviors_generic filter is_empty spec
let get_clauses = get_clauses_generic
let acsl_default () =
FreeAlloc([],[])
let safe_default kf =
if Kernel_function.has_definition kf
then FreeAlloc([],[])
else FreeAllocAny
let frama_c_default _kf =
acsl_default ()
let combine_default clauses =
(* Note: combination is made on a set of behaviors in the sens of
{!get_clauses} and {!collect_behaviors}, thus here all clauses are
[FreeAlloc ...]. *)
let collect (facc, aacc) = function
| FreeAlloc(f, a) ->
List.rev_append (List.rev f) facc, List.rev_append (List.rev a) aacc
| FreeAllocAny -> assert false
in
let f, a = List.fold_left collect ([],[]) clauses in
let f = List.sort_uniq compare_it f in
let a = List.sort_uniq compare_it a in
FreeAlloc(f, a)
let custom_default mode kf spec =
let custom_mode = get_custom_mode mode in
match custom_mode.custom_allocates.gen with
| None ->
Kernel.warning ~once:true
"Custom generation from mode %s not defined for allocates, using \
frama-c mode instead" mode;
frama_c_default kf
| Some f -> f kf spec
let emit_status kf bhv allocates status =
let ppt_opt =
Property.ip_of_allocation kf Kglobal
(Property.Id_contract (Datatype.String.Set.empty,bhv)) allocates
in
Option.iter (emit_status status) ppt_opt
let emit mode kf bhv allocates =
match mode with
| Skip -> assert false
| ACSL ->
emit_status kf bhv allocates Property_status.True
| Safe | Frama_C ->
emit_status kf bhv allocates Property_status.Dont_know
| Other mode ->
let custom_mode = get_custom_mode mode in
match custom_mode.custom_allocates.status with
| None ->
Kernel.warning ~once:true
"Custom status from mode %s not defined for allocates" mode;
()
| Some pst -> emit_status kf bhv allocates pst
end
(*****************************************************************)
(* ******************** Terminates generated ******************* *)
(* |-----------------------------------------------------------| *)
(* | ACSL | Frama-c | Safe | Other | *)
(* |-------|------|-------|------|-------|------|-------|------| *)
(* | Proto | Body | Proto | Body | Proto | Body | Proto | Body | *)
(* |-------|------|-------|------|-------|------|-------|------| *)
(* | true | true | ACSL | ACSL | false | true | ??? | ?? | *)
(* |-----------------------------------------------------------| *)
(* ***************************************************************)
(* ****** Status emitted on prototypes ******)
(* |--------------------------------------| *)
(* | ACSL | Frama-c | Safe | Other | *)
(* |------|-----------|-----------|-------| *)
(* | True | Dont_know | Dont_know | ??? | *)
(* |--------------------------------------| *)
(********************************************)
module Terminates_generator =
struct
type clause = t_terminates
type behaviors = bool
let name = "terminates"
let is_empty c = c = None
let has_default_behavior behaviors =
behaviors
let collect_behaviors spec =
None <> spec.spec_terminates
let get_clauses _spec _table = None
let acsl_default () =
Some(Logic_const.(new_predicate ptrue))
let safe_default kf =
if Kernel_function.has_definition kf
then Some(Logic_const.(new_predicate ptrue))
else Some(Logic_const.(new_predicate pfalse))
let frama_c_default _kf =
acsl_default ()
let combine_default _clauses =
assert false
let custom_default mode kf spec =
let custom_mode = get_custom_mode mode in
match custom_mode.custom_terminates.gen with
| None ->
Kernel.warning ~once:true
"Custom generation from mode %s not defined for terminates, using \
frama-c mode instead" mode;
frama_c_default kf
| Some f -> f kf spec
let emit_status kf _bhv terminates status =
match terminates with
| None -> assert false
| Some terminates ->
Property.ip_of_terminates kf Kglobal terminates
|> emit_status status
let emit mode kf bhv terminates =
match mode with
| Skip -> assert false
| ACSL ->
emit_status kf bhv terminates Property_status.True
| Safe | Frama_C ->
emit_status kf bhv terminates Property_status.Dont_know
| Other mode ->
let custom_mode = get_custom_mode mode in
match custom_mode.custom_terminates.status with
| None ->
Kernel.warning ~once:true
"Custom status from mode %s not defined for terminates" mode;
()
| Some pst -> emit_status kf bhv terminates pst
end
module Exits = Make(Exits_generator)
module Assigns = Make(Assigns_generator)
module Requires = Make(Requires_generator)
module Allocates = Make(Allocates_generator)
module Terminates = Make(Terminates_generator)
(* Convert string from parameter [-generated-spec-mode] to [mode]. *)
let get_mode ~allow_skip = function
| "frama-c" -> Frama_C
| "acsl" -> ACSL
| "safe" -> Safe
| "skip" when allow_skip -> Skip
| "skip" ->
Kernel.warning ~once:true
"Mode skip is only available via -generated-spec-custom.@, The mode \
frama-c will be used instead";
Frama_C
| s -> Other s
(* Given a [mode], returns the configuration for each clause. *)
let build_config mode =
{
c_exits = mode;
c_assigns = mode;
c_requires = mode;
c_allocates = mode;
c_terminates = mode;
}
(* Build configuration from parameter [-generated-spec-mode]. *)
let get_config_mode () =
Kernel.GeneratedSpecMode.get () |> get_mode ~allow_skip:false |> build_config
(* Build the default configuration, then select modes depending on the
parameter [-generated-spec-custom]. *)
let get_config_custom () =
let default = get_config_mode () in
let collect (k,v) config =
let mode = get_mode ~allow_skip:true (Option.get v) in
match k with
| "exits" -> {config with c_exits = mode}
| "assigns" -> {config with c_assigns = mode}
| "requires" -> {config with c_requires = mode}
| "allocates" -> {config with c_allocates = mode}
| "terminates" -> {config with c_terminates = mode}
| s ->
Kernel.abort
"@['%s'@] is not a valid key for -generated-spec-custom.@, Accepted \
keys are 'exits', 'assigns', 'requires', 'allocates' and \
'terminates'." s
in
Kernel.GeneratedSpecCustom.fold collect default
(* Create the final configuration used for the specification.
First we create the configuration to be used (either from command line
parameters, or specific modes for builtins and frama-c's stdlib).
Then we activate clauses selected for the generation, skiping all other
clauses.
*)
let activated_config kf clauses =
let default =
if is_frama_c_builtin kf then build_config Frama_C
(* TODO: Use ACSL mode for frama-c's libc once all assigns are written. *)
else if Kernel_function.is_in_libc kf then build_config Frama_C
else get_config_custom ()
in
let collect config clause =
match clause with
| `Exits -> {config with c_exits = default.c_exits}
| `Assigns -> {config with c_assigns = default.c_assigns}
| `Requires -> {config with c_requires = default.c_requires}
| `Allocates -> {config with c_allocates = default.c_allocates}
| `Terminates -> {config with c_terminates = default.c_terminates}
in
List.fold_left collect (build_config Skip) clauses
let clauses_fmt =
Pretty_utils.pp_list ~pre:"" ~sep:", " ~last:" and " ~suf:""
Format.pp_print_string
(* Emit warnings if we generated some clauses and if [kf] is a declaration
and not a builtin of frama-c (cf. {!Make.get_default}).
The message varies depending on
- if the spec was empty.
- if we used existing clauses.
- the number of generated clause types.
*)
let do_warning kf funspec = function
| None -> ()
| Some (combined, clauses) ->
let n = List.length clauses in
let clauses = Format.asprintf "%a" clauses_fmt (List.rev clauses) in
let msg =
if Cil.is_empty_funspec funspec then
Format.asprintf
"Neither code nor specification for function %a,@, \
generating default %s"
Kernel_function.pretty kf clauses
else
let source =
if combined then
if n = 1 then " from the specification"
else " (some from the specification)"
else ""
in
Format.asprintf
"Neither code nor explicit %s for function %a,@, generating default \
clauses%s"
clauses Kernel_function.pretty kf source
in
Kernel.warning
~once:true ~current:true ~wkey:Kernel.wkey_missing_spec
"%s. See -generated-spec-* options for more info" msg
(* Perform generation of all clauses, add them to the original specification,
and emit property status for each of them. *)
let do_populate kf clauses =
let original_spec = Annotations.funspec kf in
let config = activated_config kf clauses in
let apply warn_acc get_default mode =
let g, warn = get_default mode kf original_spec in
match g, warn with
| Kept, _ -> g, warn_acc
| Generated _, None -> g, warn_acc
| Generated _, Some (c, clause) ->
match warn_acc with
| None -> g, Some (c, [clause])
| Some (c', clauses) ->
g, Some (c || c', clause :: clauses)
in
let exits, warn = apply None Exits.get_default config.c_exits in
let assigns, warn = apply warn Assigns.get_default config.c_assigns in
let requires, warn = apply warn Requires.get_default config.c_requires in
let allocates, warn = apply warn Allocates.get_default config.c_allocates in
let terminates, warn = apply warn Terminates.get_default config.c_terminates in
do_warning kf original_spec warn;
let generated original = function
| Kept -> original
| Generated g -> g
in
let bhv = Cil.mk_behavior () in
let bhv = { bhv with b_post_cond = generated bhv.b_post_cond exits } in
let bhv = { bhv with b_assigns = generated bhv.b_assigns assigns } in
let bhv = { bhv with b_requires = generated bhv.b_requires requires } in
let bhv = { bhv with b_allocation = generated bhv.b_allocation allocates } in
let spec = Cil.empty_funspec () in
let spec = { spec with spec_behavior = [ bhv ] } in
let spec =
{ spec with spec_terminates = generated spec.spec_terminates terminates } in
Annotations.add_spec emitter_populate kf spec;
Exits.emit config.c_exits kf bhv exits;
Assigns.emit config.c_assigns kf bhv assigns;
Requires.emit config.c_assigns kf bhv requires;
Allocates.emit config.c_allocates kf bhv allocates;
Terminates.emit config.c_terminates kf bhv terminates
type clause = [
| `Exits
| `Assigns
| `Requires
| `Allocates
| `Terminates
]
module Clauses =
Datatype.Make
(struct
include Datatype.Serializable_undefined
type t = clause
let name = "clause"
let reprs = [`Exits; `Assigns; `Requires; `Allocates; `Terminates]
let equal a b = a = b
let compare = Stdlib.compare
let hash = Hashtbl.hash
end)
module Key =
Datatype.Pair_with_collections (Kernel_function) (Clauses)
(struct let module_name = "Populate_spec.Key.t" end)
(* Hashtbl used to memoize which kernel function has been populated. *)
module Is_populated =
State_builder.Hashtbl
(Key.Hashtbl)
(Datatype.Unit)
(struct
let size = 17
let dependencies = [ Annotations.funspec_state ]
let name = "Populate_spec.Is_populated"
end)
let () = Ast.add_linked_state Is_populated.self
(* Return the list of clauses not already done for function [kf]. *)
let todo_clauses kf clauses =
let not_populated c = not (Is_populated.mem (kf, c)) in
List.filter not_populated clauses
(* Perform specification generation for [kf] if all requirements are met :
- [clauses] is not empty
AND
[clauses] contains clauses not already generated for [kf]
- [kf] is a prototype
OR
[do_body] is true
*)
let populate_funspec ?(do_body=false) kf clauses =
let todo = todo_clauses kf clauses in
if todo <> [] then
let has_body = Kernel_function.has_definition kf in
if not has_body || do_body then begin
do_populate kf todo;
List.iter (fun c -> Is_populated.add (kf, c) ()) todo
end
let () =
(* This function is deprecated. Until removed, populate assigns and
returns true if spec generation was performed. *)
let f kf _funspec =
let before = Is_populated.mem (kf, `Assigns) in
populate_funspec kf [`Assigns];
not before && Is_populated.mem (kf, `Assigns)
in
Annotations.populate_spec_ref := f [@@ warning "-3"]