-
Andre Maroneze authoredAndre Maroneze authored
file.ml 68.85 KiB
(**************************************************************************)
(* *)
(* This file is part of Frama-C. *)
(* *)
(* Copyright (C) 2007-2021 *)
(* 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
open Cil
open Visitor
open Cil_datatype
type cpp_opt_kind = Gnu | Not_gnu | Unknown
let pretty_cpp_opt_kind fmt =
function
| Gnu -> Format.pp_print_string fmt "Gnu"
| Not_gnu -> Format.pp_print_string fmt "Not_gnu"
| Unknown -> Format.pp_print_string fmt "Unknown"
type file =
| NeedCPP of
Filepath.Normalized.t (* filename of the [.c] to preprocess *)
* string (* Preprocessing command, as given by -cpp-command, or
the default value, but without extra arguments *)
* string list (* Extra arguments to be given to the preprocessing
command, as given by -cpp-extra-args-per-file or
a JSON Compilation Database. *)
* cpp_opt_kind
| NoCPP of Filepath.Normalized.t (** filename of a preprocessed [.c] *)
| External of Filepath.Normalized.t * string
(* file * name of plug-in that handles it *)
module D =
Datatype.Make
(struct
include Datatype.Serializable_undefined
type t = file
let name = "File"
let reprs =
[ NeedCPP(Filepath.Normalized.empty, "", [], Unknown);
NoCPP Filepath.Normalized.empty;
External(Filepath.Normalized.empty, "")
]
let structural_descr = Structural_descr.t_abstract
let mem_project = Datatype.never_any_project
let copy = Datatype.identity (* immutable strings *)
let internal_pretty_code p_caller fmt t =
let pp fmt = match t with
| NoCPP s ->
Format.fprintf fmt "@[File.NoCPP %a@]" Filepath.Normalized.pretty s
| External (f,p) ->
Format.fprintf fmt "@[File.External (%a,%S)@]"
Filepath.Normalized.pretty f p
| NeedCPP (f,cmd,extra,kind) ->
Format.fprintf
fmt "@[File.NeedCPP (%a,%S,%S,%a)@]"
Filepath.Normalized.pretty f
cmd
(String.concat " " extra)
pretty_cpp_opt_kind kind
in
Type.par p_caller Type.Call fmt pp
end)
include D
let check_suffixes = Hashtbl.create 17
let new_file_type = Hashtbl.add check_suffixes
let get_suffixes () =
Hashtbl.fold
(fun s _ acc -> s :: acc)
check_suffixes
[ ".c"; ".i"; ".h" ]
let get_filepath = function NeedCPP (s, _, _, _) | NoCPP s | External (s, _) -> s
let get_name s = Filepath.Normalized.to_pretty_string (get_filepath s)
(* ************************************************************************* *)
(** {2 Preprocessor command} *)
(* ************************************************************************* *)
(* Do not trust custom command-line to be gnu like by default, but give
them a chance, with a warning indicating that things may go wrong. *)
let cpp_opt_kind () =
if Kernel.CppGnuLike.is_set () then
if Kernel.CppGnuLike.get () then Gnu else Not_gnu
else Unknown
let is_cpp_gnu_like () =
let open Fc_config in
let cpp_cmd = Kernel.CppCommand.get () in
match cpp_cmd = "", using_default_cpp, preprocessor_is_gnu_like with
| true, true, true -> Gnu
| true, true, false -> Not_gnu
| _, _, _ -> cpp_opt_kind ()
(* the preprocessor command is:
If the program has an explicit argument -cpp-command "XX -Y"
(quotes are required by the shell)
then XX -Y
else use the command in [Fc_config.preprocessor].*)
let get_preprocessor_command () =
let cmdline = Kernel.CppCommand.get() in
if cmdline <> "" then cmdline else Fc_config.preprocessor
let from_filename ?cpp f =
let cpp =
let cpp_command = Kernel.CppCommand.get() in
if cpp_command <> "" then cpp_command
else if cpp = None then get_preprocessor_command ()
else Option.get cpp
in
let extra =
let extra_by_file =
try Kernel.CppExtraArgsPerFile.find f
with Not_found -> ""
in
let jcdb_flags = Json_compilation_database.get_flags f in
if extra_by_file <> "" && jcdb_flags <> [] then
Kernel.warning ~wkey:Kernel.wkey_jcdb
"found flags for file %a@ both in -cpp-extra-args-per-file and@ \
in the json compilation database;@ the latter will be ignored"
Filepath.Normalized.pretty f;
if extra_by_file <> "" then [extra_by_file] else jcdb_flags
in
if Filename.check_suffix (f:>string) ".i" then begin
NoCPP f
end else
let suf =
try
let suf_idx = String.rindex (f:>string) '.' in
String.sub (f:>string) suf_idx (String.length (f:>string) - suf_idx)
with Not_found -> (* raised by String.rindex if '.' \notin f *)
""
in
if Hashtbl.mem check_suffixes suf then
External (f, suf)
else if cpp <> "" then begin
if not Fc_config.preprocessor_keep_comments then
Kernel.warning ~once:true
"Default pre-processor does not keep comments. Any ACSL annotation \
on non-pre-processed file will be discarded.";
NeedCPP (f, cpp, extra, is_cpp_gnu_like ())
end else
Kernel.abort "No working pre-processor found. You can only analyze \
pre-processed .i files."
(* ************************************************************************* *)
(** {2 Internal states} *)
(* ************************************************************************* *)
module Files : sig
val get: unit -> t list
val register: t list -> unit
val pre_register: t -> unit
val is_computed: unit -> bool
val reset: unit -> unit
val pre_register_state: State.t
end = struct
module S =
State_builder.List_ref
(D)
(struct
let dependencies =
[ Kernel.CppCommand.self;
Kernel.CppExtraArgs.self;
Kernel.CppExtraArgsPerFile.self;
Kernel.JsonCompilationDatabase.self;
Kernel.Files.self ]
let name = "Files for preprocessing"
end)
module Pre_files =
State_builder.List_ref
(D)
(struct
let dependencies = []
let name = "Built-ins headers and source"
end)
let () =
State_dependency_graph.add_dependencies
~from:S.self
[ Ast.self; Ast.UntypedFiles.self; Cabshelper.Comments.self ]
let () =
State_dependency_graph.add_dependencies
~from:Pre_files.self
[ Ast.self;
Ast.UntypedFiles.self;
Cabshelper.Comments.self;
Cil_builtins.Frama_c_builtins.self ]
let () =
Ast.add_linked_state Cabshelper.Comments.self
let pre_register_state = Pre_files.self
(* Allow to register files in advance, e.g. prolog files for plugins *)
let pre_register file =
let prev_files = Pre_files.get () in Pre_files.set (prev_files @ [file])
let register files =
if S.is_computed () then
raise (Ast.Bad_Initialization "[File.register] Too many initializations");
let prev_files = S.get () in
S.set (prev_files @ files);
S.mark_as_computed ()
let get () = Pre_files.get () @ S.get ()
let is_computed () = S.is_computed ()
let reset () =
let selection = State_selection.with_dependencies S.self in
(* Keep built-in files set *)
Project.clear ~selection ()
end
let get_all = Files.get
let pre_register = Files.pre_register
(* ************************************************************************* *)
(** {2 Machdep} *)
(* ************************************************************************* *)
(* not exported, see [pretty_machdep] below. *)
let print_machdep fmt (m : Cil_types.mach) =
begin
let open Cil_types in
Format.fprintf fmt "Machine: %s@\n" m.version ;
let pp_size_error fmt v =
if v < 0
then Format.pp_print_string fmt "error"
else Format.fprintf fmt "%2d" v
in
let pp_size_bits fmt v =
if v >= 0 then Format.fprintf fmt "%d bits, " (v*8)
in
let pp_align_error fmt v =
if v < 0
then Format.pp_print_string fmt "alignof error"
else Format.fprintf fmt "aligned on %d bits" (v*8)
in
List.iter
(fun (name,size,align) ->
Format.fprintf fmt
" sizeof %11s = %a (%a%a)@\n"
name pp_size_error size pp_size_bits size pp_align_error align)
[
"short", m.sizeof_short, m.alignof_short ;
"int", m.sizeof_int, m.alignof_int ;
"long", m.sizeof_long, m.alignof_long ;
"long long", m.sizeof_longlong, m.alignof_longlong ;
"float", m.sizeof_float, m.alignof_float ;
"double", m.sizeof_double, m.alignof_double ;
"long double", m.sizeof_longdouble, m.alignof_longdouble ;
"pointer", m.sizeof_ptr, m.alignof_ptr ;
"void", m.sizeof_void, m.sizeof_void ;
"function", m.sizeof_fun, m.alignof_fun ;
] ;
List.iter
(fun (name,typeof) ->
Format.fprintf fmt " typeof %11s = %s@\n" name typeof)
[
"sizeof(T)", m.size_t ;
"wchar_t", m.wchar_t ;
"ptrdiff_t", m.ptrdiff_t ;
] ;
Format.fprintf fmt " char is %s@\n"
(if m.char_is_unsigned then "unsigned" else "signed");
Format.fprintf fmt " machine is %s endian@\n"
(if m.little_endian then "little" else "big") ;
Format.fprintf fmt " strings are %s chars@\n"
(if m.const_string_literals then "const" else "writable") ;
Format.fprintf fmt " assembly names %s leading '_'@\n"
(if m.underscore_name then "have" else "have no") ;
Format.fprintf fmt " compiler %s builtin __va_list@\n"
(if m.has__builtin_va_list then "has" else "has not") ;
end
module DatatypeMachdep = Datatype.Make_with_collections(struct
include Datatype.Serializable_undefined
let reprs = [Machdeps.x86_32]
let name = "File.Machdep"
type t = Cil_types.mach
let compare : t -> t -> int = Stdlib.compare
let equal : t -> t -> bool = (=)
let hash : t -> int = Hashtbl.hash
let copy = Datatype.identity
end)
let default_machdeps =
[ "x86_16", Machdeps.x86_16;
"x86_32", Machdeps.x86_32;
"x86_64", Machdeps.x86_64;
"gcc_x86_16", Machdeps.x86_16;
"gcc_x86_32", Machdeps.gcc_x86_32;
"gcc_x86_64", Machdeps.gcc_x86_64;
"ppc_32", Machdeps.ppc_32;
"msvc_x86_64", Machdeps.msvc_x86_64;
]
let regexp_existing_machdep_macro = Str.regexp "-D[ ]*__FC_MACHDEP_"
let existing_machdep_macro () =
let extra = String.concat " " (Kernel.CppExtraArgs.get ()) in
try
ignore (Str.search_forward regexp_existing_machdep_macro extra 0);
true
with Not_found -> false
let machdep_macro = function
| "x86_16" -> "__FC_MACHDEP_X86_16"
| "gcc_x86_16" -> "__FC_MACHDEP_GCC_X86_16"
| "x86_32" -> "__FC_MACHDEP_X86_32"
| "gcc_x86_32" -> "__FC_MACHDEP_GCC_X86_32"
| "x86_64" -> "__FC_MACHDEP_X86_64"
| "gcc_x86_64" -> "__FC_MACHDEP_GCC_X86_64"
| "ppc_32" -> "__FC_MACHDEP_PPC_32"
| "msvc_x86_64" -> "__FC_MACHDEP_MSVC_X86_64"
| s ->
let res = "__FC_MACHDEP_" ^ (String.uppercase_ascii s) in
Kernel.warning ~once:true
"machdep %s has no registered macro. Using %s for pre-processing" s res;
res
module Machdeps =
State_builder.Hashtbl(Datatype.String.Hashtbl)(DatatypeMachdep)
(struct
let name = " File.Machdeps"
let size = 5
let dependencies = []
end)
let mem_machdep s = Machdeps.mem s || List.mem_assoc s default_machdeps
let new_machdep s m =
try
let cm = Machdeps.find s in
if not (cm = m) then
Kernel.abort "trying to register incompatible machdeps under name `%s'" s
with Not_found ->
Machdeps.add s m
let pretty_machdeps fmt =
Machdeps.iter (fun x _ -> Format.fprintf fmt "@ %s" x);
List.iter (fun (x, _) -> Format.fprintf fmt "@ %s" x) default_machdeps
let machdep_help () =
let m = Kernel.Machdep.get () in
if m = "help" then begin
Kernel.feedback
"@[supported machines are%t@ (default is x86_32).@]"
pretty_machdeps;
raise Cmdline.Exit
end else
Cmdline.nop
let () = Cmdline.run_after_exiting_stage machdep_help
let set_machdep () =
let m = Kernel.Machdep.get () in
if not (mem_machdep m) then
Kernel.abort "@[unsupported machine %s.@ Try one of%t.@]" m pretty_machdeps
let () = Cmdline.run_after_configuring_stage set_machdep
(* Local to this module. Use Cil.theMachine.theMachine outside *)
let get_machdep () =
let m = Kernel.Machdep.get () in
try
Machdeps.find m
with Not_found ->
try
List.assoc m default_machdeps
with Not_found -> (* Should not happen given the checks above *)
Kernel.fatal "Machdep %s not registered" m
let list_available_machdeps () =
Machdeps.fold (fun m _ acc -> m :: acc) (List.map fst default_machdeps)
let pretty_machdep ?fmt ?machdep () =
let machine = match machdep with None -> get_machdep () | Some m -> m in
match fmt with
| None -> Log.print_on_output (fun fmt -> print_machdep fmt machine)
| Some fmt -> print_machdep fmt machine
(* ************************************************************************* *)
(** {2 Initializations} *)
(* ************************************************************************* *)
let create_temp_file ?debug name suffix =
let of_string = Filepath.Normalized.of_string in
try of_string (Extlib.temp_file_cleanup_at_exit ?debug name suffix)
with Extlib.Temp_file_error s ->
Kernel.abort "cannot create temporary file: %s" s
let safe_remove_file (f : Datatype.Filepath.t) =
if not (Kernel.is_debug_key_enabled Kernel.dkey_parser) then
Extlib.safe_remove (f :> string)
let replace_in_cpp_cmd cmdl supp_args in_file out_file =
(* using Filename.quote for filenames which contain space or shell
metacharacters *)
let in_file = Filename.quote in_file
and out_file = Filename.quote out_file in
let substitute s =
match Str.matched_string s with
| "%%" -> "%"
| "%args" -> supp_args
| "%1" | "%i" | "%input" -> in_file
| "%2" | "%o" | "%output" -> out_file
| s -> s (* Unrecognized parameters are left intact *)
in
let regexp = Str.regexp "%%\\|%[a-z0-9]+" in
try
ignore (Str.search_forward regexp cmdl 0); (* Try to find one match *)
Str.global_substitute regexp substitute cmdl
with Not_found ->
Format.sprintf "%s %s %s -o %s" cmdl supp_args in_file out_file
(* Note: using Pretty_utils.pp_list without forcing '~pre' and '~suf' to
be empty strings can lead to issues when the commands are too long and
Format's pretty-printer decides to insert newlines.
This concatenation function serves as a reminder to avoid using them.
*)
let concat_strs ?(pre="") ?(sep=" ") l =
if l = [] then ""
else pre ^ (String.concat sep l)
let build_cpp_cmd = function
| NoCPP _ | External _ -> None
| NeedCPP (f, cmdl, extra, is_gnu_like) ->
if not (Sys.file_exists (f :> string)) then
Kernel.abort "source file %a does not exist"
Filepath.Normalized.pretty f;
let debug = Kernel.is_debug_key_enabled Kernel.dkey_parser in
let add_if_gnu opt =
match is_gnu_like with
| Gnu -> [opt]
| Not_gnu -> []
| Unknown ->
Kernel.warning
~once:true
"your preprocessor is not known to handle option `%s'. \
If pre-processing fails because of it, please add \
-no-cpp-frama-c-compliant option to Frama-C's command-line. \
If you do not want to see this warning again, explicitly use \
option -cpp-frama-c-compliant."
opt;
[opt]
in
let ppf = create_temp_file ~debug (Filename.basename (f :> string)) ".i" in
(* Hypothesis: the preprocessor is POSIX compliant,
hence understands -I and -D. *)
let include_args =
if Kernel.FramaCStdLib.get () then [(Fc_config.framac_libc:>string)]
else []
in
let define_args =
if not (existing_machdep_macro ())
then [machdep_macro (Kernel.Machdep.get ())]
else []
in
let define_args = "__FRAMAC__" :: define_args in
(* Hypothesis: the preprocessor does support the arch-related
options tested when 'configure' was run. *)
let required_cpp_arch_args = (get_machdep ()).cpp_arch_flags in
let supported_cpp_arch_args, unsupported_cpp_arch_args =
List.partition (fun arg ->
List.mem arg Fc_config.preprocessor_supported_arch_options)
required_cpp_arch_args
in
if is_gnu_like = Unknown && not (Kernel.CppCommand.is_set ())
&& unsupported_cpp_arch_args <> [] then
Kernel.warning ~once:true
"your preprocessor is not known to handle option(s) `%s', \
considered necessary for machdep `%s'. To ensure compatibility \
between your preprocessor and the machdep, consider using \
-cpp-command with the appropriate flags. \
Your preprocessor is known to support these flags: %s"
(concat_strs unsupported_cpp_arch_args) (Kernel.Machdep.get ())
(concat_strs Fc_config.preprocessor_supported_arch_options);
let nostdinc_arg =
if Kernel.FramaCStdLib.get() then add_if_gnu "-nostdinc"
else []
in
let output_defines_arg =
let open Kernel in
match ReadAnnot.get (), PreprocessAnnot.is_set (), PreprocessAnnot.get () with
| true, true, true -> ["-dD"] (* keep '#defines' in preprocessed output *)
| true, true, false -> []
| true, false, _ -> add_if_gnu "-dD"
| _, _, _ -> []
in
let gnu_implicit_args = output_defines_arg @ nostdinc_arg in
let string_of_supp_args extra includes defines =
Format.asprintf "%s%s%s"
(concat_strs ~pre:"-I" ~sep:" -I" includes)
(concat_strs ~pre:" -D" ~sep:" -D" defines)
(concat_strs ~pre:" " ~sep:" " extra)
in
let supp_args =
string_of_supp_args
(Kernel.CppExtraArgs.get () @ gnu_implicit_args @ supported_cpp_arch_args @ extra)
include_args define_args
in
let cpp_command = replace_in_cpp_cmd cmdl supp_args (f:>string) (ppf:>string) in
let cpp_command_with_chdir =
if Kernel.JsonCompilationDatabase.is_set () then
let jcdb_path = (Kernel.JsonCompilationDatabase.get () :> string) in
let dir =
if Sys.is_directory jcdb_path then jcdb_path
else Filename.dirname jcdb_path
in
(* TODO: we currently use PWD instead of Sys.getcwd () because OCaml has
no function in its stdlib to resolve symbolic links (e.g. realpath)
for a given path. 'getcwd' always resolves them, but if the user
supplies a path with symbolic links, this may cause issues.
Instead of forcing the user to always provide resolved paths, we
currently choose to never resolve them. *)
let cwd = Unix.getenv "PWD" in
if cwd <> dir then
"cd " ^ dir ^ " && " ^ cpp_command
else cpp_command
else cpp_command
in
Kernel.feedback ~dkey:Kernel.dkey_pp
"preprocessing with \"%s\""
cpp_command_with_chdir;
Some (cpp_command_with_chdir, ppf, supp_args)
let parse_cabs cpp_command = function
| NoCPP f ->
if not (Sys.file_exists (f:>string)) then
Kernel.abort "preprocessed file %a does not exist"
Filepath.Normalized.pretty f;
Kernel.feedback "Parsing %a (no preprocessing)"
Datatype.Filepath.pretty f;
Frontc.parse f ()
| NeedCPP (f, cmdl, _extra, is_gnu_like) ->
let cpp_command, ppf, supp_args = Option.get cpp_command in
Kernel.feedback "Parsing %a (with preprocessing)"
Datatype.Filepath.pretty f;
if Sys.command cpp_command <> 0 then begin
safe_remove_file ppf;
let possible_cause =
if Kernel.JsonCompilationDatabase.is_set () then
if not (Json_compilation_database.has_entry f) then
Format.asprintf "note: %s is set but \
contains no entries for '%a'.@ "
Kernel.JsonCompilationDatabase.option_name
Datatype.Filepath.pretty f
else ""
else
if not (Kernel.CppExtraArgs.is_set ()) &&
not (Kernel.CppExtraArgsPerFile.is_set ()) &&
not (Kernel.CppCommand.is_set ()) then
Format.asprintf
"this is possibly due to missing preprocessor flags;@ \
consider options %s, %s or %s.@ "
Kernel.CppExtraArgs.option_name
Kernel.JsonCompilationDatabase.option_name
Kernel.CppCommand.option_name
else ""
in
Kernel.abort
"failed to run: %s@\n\
%sSee chapter \"Preparing the Sources\" in the Frama-C user manual \
for more details."
cpp_command possible_cause
end;
let ppf =
if Kernel.ReadAnnot.get() &&
((Kernel.PreprocessAnnot.is_set () &&
Kernel.PreprocessAnnot.get())
|| (match is_gnu_like with
| Gnu -> true
| Not_gnu -> false
| Unknown ->
Kernel.warning
~once:true
"trying to preprocess annotation with an unknown \
preprocessor."; true))
then begin
let ppf' =
try Logic_preprocess.file ".c"
(replace_in_cpp_cmd cmdl supp_args)
(ppf : Filepath.Normalized.t :> string)
with Sys_error _ as e ->
safe_remove_file ppf;
Kernel.abort "preprocessing of annotations failed (%s)"
(Printexc.to_string e)
in
safe_remove_file ppf ;
ppf'
end else ppf
in
let (cil,(_,defs)) = Frontc.parse ppf () in
cil.fileName <- f;
safe_remove_file ppf;
(cil,(f,defs))
| External (f,suf) ->
if not (Sys.file_exists (f:>string)) then
Kernel.abort "file %a does not exist."
Filepath.Normalized.pretty f;
try
Kernel.feedback "Parsing %a (external front-end)"
Datatype.Filepath.pretty f;
Hashtbl.find check_suffixes suf (f:>string)
with Not_found ->
Kernel.abort
"could not find a suitable plugin for parsing %a."
Filepath.Normalized.pretty f
let to_cil_cabs cpp_cmds_and_args f =
try
let cpp_command = List.assoc f cpp_cmds_and_args in
let a,c = parse_cabs cpp_command f in
Kernel.debug ~dkey:Kernel.dkey_file_print_one "result of parsing %s:@\n%a"
(get_name f) Cil_printer.pp_file a;
if Errorloc.had_errors () then raise Exit;
a, c
with exn when Errorloc.had_errors () ->
if Kernel.Debug.get () >= 1 then raise exn
else
Kernel.abort "@[stopping on@ file %S@ that@ has@ errors.%t@]"
(get_name f)
(fun fmt ->
if Filename.check_suffix (get_name f :> string) ".c" &&
not (Kernel.is_debug_key_enabled Kernel.dkey_pp)
then
Format.fprintf fmt "@ Add@ '-kernel-msg-key pp'@ \
for preprocessing command.")
let () =
let handle f =
let preprocess =
replace_in_cpp_cmd (get_preprocessor_command ()) "-nostdinc"
in
let ppf =
try Logic_preprocess.file ".c" preprocess f
with Sys_error _ as e ->
Kernel.abort "preprocessing of annotations failed (%s)"
(Printexc.to_string e)
in
let path = Datatype.Filepath.of_string f in
let (cil,(_,defs)) = Frontc.parse ppf () in
cil.fileName <- path;
safe_remove_file ppf;
(cil,(path,defs))
in
new_file_type ".ci" handle
(* Keep defined entry point even if not defined, and possibly
other unused globals according to relevant command-line parameters.
This function is meant to be passed to {!Rmtmps.removeUnused}. *)
let isRoot g =
let specs = Kernel.Keep_unused_specified_functions.get () in
let keepTypes = Kernel.Keep_unused_types.get () in
Rmtmps.isExportedRoot g ||
match g with
| GFun({svar = v; sspec = spec},_)
| GFunDecl(spec,v,_) ->
Kernel.MainFunction.get_plain_string () = v.vname
(* Always keep the declaration of the entry point *)
|| (specs && not (is_empty_funspec spec))
(* and the declarations carrying specifications according to the
command line.*)
| GType _ | GCompTag _ | GCompTagDecl _ | GEnumTag _ | GEnumTagDecl _ ->
keepTypes
| _ -> false
let files_to_cabs_cil files cpp_commands =
Kernel.feedback ~level:2 "parsing";
(* Parsing and merging must occur in the very same order.
Otherwise the order of files on the command line will not be consistently
handled. *)
let cil_cabs = List.fold_left (fun acc f -> to_cil_cabs cpp_commands f :: acc) [] files in
let cil_files, cabs_files = List.split cil_cabs in
(* fold_left reverses the list order.
This is an issue with pre-registered files. *)
let cil_files = List.rev cil_files in
let cabs_files = List.rev cabs_files in
Ast.UntypedFiles.set cabs_files;
(* Perform symbolic merge of all files *)
Kernel.feedback ~level:2 "symbolic link";
let merged_file = Mergecil.merge cil_files "whole_program" in
Logic_utils.complete_types merged_file;
if Kernel.UnspecifiedAccess.get () then
Undefined_sequence.check_sequences merged_file;
merged_file, cabs_files
(* "Implicit" annotations are those added by the kernel with ACSL name
'Frama_C_implicit_init'. Currently, this concerns statements that are
generated to initialize local variables. *)
module Implicit_annotations =
State_builder.Hashtbl
(Property.Hashtbl)(Datatype.List(Property))
(struct
let name = "File.Implicit_annotations"
let dependencies = [Annotations.code_annot_state]
let size = 32
end)
let () = Ast.add_linked_state Implicit_annotations.self
let () =
Property_status.register_property_remove_hook
(fun p ->
if Implicit_annotations.mem p then begin
Kernel.debug ~dkey:Kernel.dkey_file_annot
"Removing implicit property %a" Property.pretty p;
Implicit_annotations.remove p
end)
let emit_status p hyps =
Kernel.debug ~dkey:Kernel.dkey_file_annot
"Marking implicit property %a as true" Property.pretty p;
Property_status.emit Emitter.kernel ~hyps p Property_status.True
let emit_all_statuses _ =
Kernel.debug ~dkey:Kernel.dkey_file_annot "Marking properties";
Implicit_annotations.iter emit_status
let () = Ast.apply_after_computed emit_all_statuses
let add_annotation kf st a =
Annotations.add_code_annot ~keep_empty:false Emitter.end_user ~kf st a;
(* Now check if the annotation is valid by construction
(provided normalization is correct). *)
match a.annot_content with
| AStmtSpec
([],
({ spec_behavior = [ { b_name = "Frama_C_implicit_init" } as bhv]})) ->
let props = Property.ip_post_cond_of_behavior kf (Kstmt st) [] bhv in
List.iter (fun p -> Implicit_annotations.add p []) props
| _ -> ()
let synchronize_source_annot has_new_stmt kf =
match kf.fundec with
| Definition (fd,_) ->
let (visitor:cilVisitor) = object
inherit nopCilVisitor as super
val block_with_user_annots = ref None
val user_annots_for_next_stmt = ref []
method! vstmt st =
super#pop_stmt st;
let father = super#current_stmt in
super#push_stmt st;
let is_in_same_block () = match !block_with_user_annots,father with
| None, None -> true
| Some block, Some stmt_father when block == stmt_father -> true
| _, _ -> false
in
let synchronize_user_annot a = add_annotation kf st a in
let synchronize_previous_user_annots () =
if !user_annots_for_next_stmt <> [] then begin
if is_in_same_block () then begin
let my_annots = !user_annots_for_next_stmt in
let post_action st =
let treat_annot (has_annot,st) (st_ann, annot) =
if Logic_utils.is_annot_next_stmt annot then begin
if has_annot || st.labels <> [] || st_ann.labels <> []
then begin
st_ann.skind <- (Block (Cil.mkBlockNonScoping [st]));
has_new_stmt := true;
Annotations.add_code_annot
~keep_empty:false Emitter.end_user ~kf st_ann annot;
(true, st_ann)
end else begin
add_annotation kf st annot;
(true,st)
end
end else begin
add_annotation kf st annot;
(true, st)
end
in
let (_,st) =
List.fold_left treat_annot (false,st) my_annots
in
st
in
block_with_user_annots:=None;
user_annots_for_next_stmt:=[];
ChangeDoChildrenPost(st,post_action)
end
else begin
Kernel.warning ~current:true ~once:true
"Ignoring previous annotation relative \
to next statement effects" ;
block_with_user_annots := None ;
user_annots_for_next_stmt := [];
DoChildren
end
end else begin
block_with_user_annots := None ;
user_annots_for_next_stmt := [];
DoChildren;
end
in
let add_user_annot_for_next_stmt st annot =
if !user_annots_for_next_stmt = [] then begin
block_with_user_annots := father;
user_annots_for_next_stmt := [st,annot]
end else if is_in_same_block () then
user_annots_for_next_stmt :=
(st, annot)::!user_annots_for_next_stmt
else begin
Kernel.warning ~current:true ~once:true
"Ignoring previous annotation relative to next statement \
effects";
block_with_user_annots := father;
user_annots_for_next_stmt := [st, annot] ;
end;
ChangeTo (Cil.mkStmtOneInstr (Skip Cil_datatype.Location.unknown))
in
assert (!block_with_user_annots = None
|| !user_annots_for_next_stmt <> []);
match st.skind with
| Instr (Code_annot (annot,_)) ->
(* Code annotation isn't considered as a real stmt.
So, previous annotations should be relative to the next stmt.
Only this [annot] may be synchronised to that stmt *)
if Logic_utils.is_annot_next_stmt annot then
(* Annotation relative to the effect of next statement *)
add_user_annot_for_next_stmt st annot
else
(* Annotation relative to the current control point *)
(match !user_annots_for_next_stmt with
| [] -> synchronize_user_annot annot; DoChildren
| _ ->
(* we have an annotation relative to the next
real C statement somewhere above, and we have
not reached it yet. Just stack the current annotation.*)
add_user_annot_for_next_stmt st annot)
| Loop (annot, _, _, _, _) ->
(* Synchronize previous annotations on that statement *)
let res = synchronize_previous_user_annots () in
(* Synchronize loop annotations on that statement *)
List.iter synchronize_user_annot
(List.sort (fun x y -> x.annot_id - y.annot_id) annot);
res
| _ ->
(* Synchronize previous annotations on that statement *)
synchronize_previous_user_annots () ;
end
in
ignore (visitCilFunction visitor fd)
| Declaration _ -> ()
let register_global = function
| GFun (fundec, loc) ->
let onerets = ref [] in
let callback return goto = onerets := (return,goto) :: !onerets in
(* ensure there is only one return *)
Oneret.oneret ~callback fundec;
(* Build the Control Flow Graph for all
functions *)
if Kernel.SimplifyCfg.get () then begin
Cfg.prepareCFG ~keepSwitch:(Kernel.KeepSwitch.get ()) fundec;
Cfg.clearCFGinfo fundec;
Cfg.cfgFun fundec;
(* prepareCFG may add additional labels that are not used in the end. *)
Rmtmps.remove_unused_labels fundec;
end;
Globals.Functions.add (Definition(fundec,loc));
let kf = Globals.Functions.get fundec.svar in
(* Finally set property-status on oneret clauses *)
List.iter
(fun ((sret,b,pret),gotos) ->
let ipreturns =
Property.ip_of_ensures kf (Kstmt sret) b (Returns,pret) in
let ipgotos = List.map
(fun (sgot,agot) -> Property.ip_of_code_annot_single kf sgot agot)
gotos in
Implicit_annotations.add ipreturns ipgotos
) !onerets ;
| GFunDecl (spec, f,loc) ->
(* global prototypes *)
let args =
try Some (Cil.getFormalsDecl f) with Not_found -> None
in
(* Use a copy of the spec, as the original one will be erased by
AST cleanup. *)
let spec = { spec with spec_variant = spec.spec_variant } in
Globals.Functions.add (Declaration(spec,f,args,loc))
| GVarDecl (vi,_) when not vi.vdefined ->
(* global variables declaration with no definitions *)
Globals.Vars.add_decl vi
| GVar (varinfo,initinfo,_) ->
(* global variables definitions *)
Globals.Vars.add varinfo initinfo;
| GAnnot (annot,_loc) ->
Annotations.add_global Emitter.end_user annot
| _ -> ()
let computeCFG ~clear_id file =
Cfg.clearFileCFG ~clear_id file;
Cfg.computeFileCFG file
(* Remove (inplace) annotations that are physically in the AST (and that have
been moved inside kernel tables) by turning them into Skip, then
remove empty statements and blocks. *)
let cleanup file =
let visitor = object(self)
inherit Visitor.frama_c_inplace
val mutable keep_stmt = Stmt.Set.empty
val mutable changed = false
method private remove_lexical_annotations stmt =
match stmt.skind with
| Instr(Code_annot(_,loc)) ->
stmt.skind <- Instr(Skip(loc))
| Loop (_::_, b1,l1,s1,s2) ->
stmt.skind <- Loop ([], b1, l1, s1, s2)
| _ -> ()
method! vstmt_aux st =
self#remove_lexical_annotations st;
let loc = Stmt.loc st in
if Annotations.has_code_annot st || st.labels <> [] || st.sattr <> [] then
keep_stmt <- Stmt.Set.add st keep_stmt;
match st.skind with
| Block b ->
(* queue is flushed afterwards*)
let b' = Cil.visitCilBlock (self:>cilVisitor) b in
(match b'.bstmts, b'.blocals, b'.bstatics with
| [], [], [] -> changed <- true; st.skind <- (Instr (Skip loc))
| _ -> if b != b' then st.skind <- Block b');
SkipChildren
| _ -> DoChildren
method! vblock b =
let optim b =
b.bstmts <-
List.filter
(fun x ->
not (Cil.is_skip x.skind) || Stmt.Set.mem x keep_stmt ||
( changed <- true; false) (* don't try this at home, kids...*)
)
b.bstmts;
(* Now that annotations are in the table, we do not need to
retain the block anymore.
*)
b.battrs <- List.filter
(function
| Attr(l,[]) when l = Cabs2cil.frama_c_keep_block -> false
| _ -> true)
b.battrs;
b
in
(* uncomment if you don't want to consider scope of locals (see below) *)
(* b.blocals <- [];*)
ChangeDoChildrenPost(b,optim)
method! vglob_aux = function
| GFun (f,_) ->
f.sspec <- Cil.empty_funspec ();
(* uncomment if you dont want to treat scope of locals (see above)*)
(* f.sbody.blocals <- f.slocals; *)
DoChildren
| GFunDecl(s,_,_) ->
Logic_utils.clear_funspec s;
DoChildren
| GType _ | GCompTag _ | GCompTagDecl _ | GEnumTag _
| GEnumTagDecl _ | GVar _ | GVarDecl _ | GAsm _ | GPragma _ | GText _
| GAnnot _ ->
SkipChildren
method! vfile f =
ChangeDoChildrenPost
(f,fun f -> if changed then begin
Cfg.clearFileCFG ~clear_id:false f;
Cfg.computeFileCFG f; f end
else f)
end
in visitFramacFileSameGlobals visitor file
let print_renaming: Cil.cilVisitor = object
inherit Cil.nopCilVisitor
method! vvdec v =
if v.vname <> v.vorig_name then begin
Kernel.result ~current:true
"Variable %s has been renamed to %s" v.vorig_name v.vname
end;
DoChildren
end
module Transform_before_cleanup =
Hook.Build_ordered
(struct module Id = Datatype.String type t = Cil_types.file end)
module Transform_after_cleanup =
Hook.Build_ordered
(struct module Id = Datatype.String type t = Cil_types.file end)
module Transform_after_parameter_change =
Hook.Build_ordered
(struct module Id = Datatype.String type t = State.t end)
let transform_parameters = ref State.Set.empty
type code_transformation_category =
{ name: string;
before_id: Transform_before_cleanup.id;
after_id: Transform_after_cleanup.id;
prm_id: Transform_after_parameter_change.id }
let register_code_transformation_category s =
{ name = s;
before_id = Transform_before_cleanup.register_key s;
after_id = Transform_after_cleanup.register_key s;
prm_id = Transform_after_parameter_change.register_key s }
module Cfg_recomputation_queue =
State_builder.Set_ref(Cil_datatype.Fundec.Set)
(struct
let name = "File.Cfg_recomputation_queue"
let dependencies = [Ast.self]
end)
let () = Ast.add_linked_state Cfg_recomputation_queue.self
let must_recompute_cfg f = Cfg_recomputation_queue.add f
let recompute_cfg _ =
(* just in case f happens to modify the CFG *)
Cfg_recomputation_queue.iter
(fun f -> Cfg.clearCFGinfo ~clear_id:false f; Cfg.cfgFun f);
Cfg_recomputation_queue.clear ()
let add_transform_parameter
~before ~after name f (p:(module Parameter_sig.S)) =
let module P = (val p: Parameter_sig.S) in
let hook self =
(* hook is launched if AST already exists and the apply was triggered by
the corresponding option change *)
if State.equal self P.self && Ast.is_computed () then begin
Kernel.feedback ~dkey:Kernel.dkey_file_transform
"applying %s to current AST, after option %s changed"
name.name P.option_name;
f (Ast.get());
recompute_cfg ();
if Kernel.Check.get () then
Filecheck.check_ast
("after code transformation: " ^ name.name ^
" triggered by " ^ P.option_name)
end
in
(* P.add_set_hook must be done only once. *)
if not (State.Set.mem P.self !transform_parameters) then begin
transform_parameters:=State.Set.add P.self !transform_parameters;
P.add_set_hook (fun _ _ -> Transform_after_parameter_change.apply P.self)
end;
Transform_after_parameter_change.extend name.prm_id hook;
List.iter
(fun b ->
Transform_after_parameter_change.add_dependency name.prm_id b.prm_id)
before;
List.iter
(fun a ->
Transform_after_parameter_change.add_dependency a.prm_id name.prm_id)
after
let transform_and_check name is_normalized f ast =
let printer =
if is_normalized then Printer.pp_file else Cil_printer.pp_file
in
Kernel.feedback
~dkey:Kernel.dkey_file_transform
"applying %s to file:@\n%a" name printer ast;
f ast;
recompute_cfg ();
if Kernel.Check.get () then begin
Filecheck.check_ast
~is_normalized ~ast ("after code transformation: " ^ name);
end
let add_code_transformation_before_cleanup
?(deps:(module Parameter_sig.S) list = [])
?(before=[]) ?(after=[]) name f =
Transform_before_cleanup.extend
name.before_id (transform_and_check name.name false f);
List.iter
(fun b ->
Transform_before_cleanup.add_dependency name.before_id b.before_id)
before;
List.iter
(fun a ->
Transform_before_cleanup.add_dependency a.before_id name.before_id)
after;
List.iter (add_transform_parameter ~before ~after name f) deps
let add_code_transformation_after_cleanup
?(deps:(module Parameter_sig.S) list = []) ?(before=[]) ?(after=[])
name f =
Transform_after_cleanup.extend name.after_id
(transform_and_check name.name true f);
List.iter
(fun b ->
Transform_after_cleanup.add_dependency name.after_id b.after_id) before;
List.iter
(fun a ->
Transform_after_cleanup.add_dependency a.after_id name.after_id) after;
List.iter (add_transform_parameter ~before ~after name f) deps
let syntactic_constant_folding ast =
if Kernel.Constfold.get () then
Cil.visitCilFileSameGlobals (Cil.constFoldVisitor true) ast
let constfold = register_code_transformation_category "constfold"
let () =
let deps = [ (module Kernel.Constfold: Parameter_sig.S) ] in
add_code_transformation_after_cleanup
~deps constfold syntactic_constant_folding
let () =
let constglobfold = register_code_transformation_category "constglobfold" in
let syntactic_const_globals_substitution ast =
if Kernel.Constfold.get ()
then
Cil.visitCilFileSameGlobals
Substitute_const_globals.constGlobSubstVisitor
ast
in
add_code_transformation_before_cleanup
~deps:[ (module Kernel.Constfold: Parameter_sig.S) ]
constglobfold
syntactic_const_globals_substitution
let prepare_cil_file ast =
Kernel.feedback ~level:2 "preparing the AST";
computeCFG ~clear_id:true ast;
if Kernel.Check.get () then begin
Filecheck.check_ast ~is_normalized:false ~ast "initial AST"
end;
Kernel.feedback ~level:2 "First check done";
if Kernel.Orig_name.get () then begin
Cil.visitCilFileSameGlobals print_renaming ast
end;
Transform_before_cleanup.apply ast;
(* Remove unused temp variables and globals. *)
Kernel.feedback ~level:2 "cleaning unused parts";
Rmtmps.removeUnused ~isRoot ast;
if Kernel.Check.get () then begin
Filecheck.check_ast ~is_normalized:false ~ast "Removed temp vars"
end;
(try
List.iter register_global ast.globals
with Globals.Vars.AlreadyExists(vi,_) ->
Kernel.fatal
"Trying to add the same varinfo twice: %a (vid:%d)"
Printer.pp_varinfo vi vi.vid);
Kernel.feedback ~level:2 "register globals done";
(* NB: register_global also calls oneret, which might introduce new
statements and new annotations tied to them. Since sid are set by cfg,
we must compute it again before annotation synchronisation *)
Cfg.clearFileCFG ~clear_id:false ast;
Cfg.computeFileCFG ast;
let recompute = ref false in
Globals.Functions.iter (synchronize_source_annot recompute);
(* We might also introduce new blocks for synchronization. *)
if !recompute then begin
Cfg.clearFileCFG ~clear_id:false ast;
Cfg.computeFileCFG ast;
end;
cleanup ast;
Ast.set_file ast;
(* Check that normalization is correct. *)
if Kernel.Check.get() then begin
Filecheck.check_ast ~ast "AST after normalization";
end;
Globals.Functions.iter Annotations.register_funspec;
if Kernel.Check.get () then begin
Filecheck.check_ast ~ast "AST after annotation registration"
end;
Transform_after_cleanup.apply ast;
(* reset tables depending on AST in case they have been computed during
the transformation. *)
Ast.set_file ast
let fill_built_ins () =
if Cil.selfMachine_is_computed () then begin
Kernel.debug "Machine is computed, just fill the built-ins";
Cil_builtins.init_builtins ();
end else begin
Kernel.debug "Machine is not computed, initialize everything";
Cil.initCIL (Logic_builtin.init()) (get_machdep ());
end;
(* Fill logic tables with builtins *)
Logic_env.Builtins.apply ();
Logic_env.prepare_tables ()
let init_project_from_cil_file prj file =
let selection =
State_selection.diff
State_selection.full
(State_selection.list_union
(List.map State_selection.with_dependencies
[Cil_builtins.Builtin_functions.self;
Ast.self;
Files.pre_register_state]))
in
Project.copy ~selection prj;
Project.on prj (fun file -> fill_built_ins (); prepare_cil_file file) file
let files_pre_register_state = Files.pre_register_state
module Global_annotation_graph = struct
module Base =
Graph.Imperative.Digraph.Concrete(Cil_datatype.Global)
include Base
include Graph.Traverse.Dfs(Base)
include Graph.Topological.Make(Base)
end
let find_typeinfo ty =
let module F = struct exception Found of global end in
let globs = (Ast.get()).globals in
try
List.iter
(fun g -> match g with
| GType (ty',_) when ty == ty' -> raise (F.Found g)
| GType (ty',_) when ty.tname = ty'.tname ->
Kernel.fatal
"Lost sharing between definition and declaration of type %s"
ty.tname
| _ -> ())
globs;
Kernel.fatal "Reordering AST: unknown typedef for %s" ty.tname
with F.Found g -> g
let extract_logic_infos g =
let rec aux acc = function
| Dfun_or_pred (li,_) | Dinvariant (li,_) | Dtype_annot (li,_) -> li :: acc
| Dvolatile _ | Dtype _ | Dlemma _
| Dmodel_annot _ | Dcustom_annot _ | Dextended _ -> acc
| Daxiomatic(_,l,_,_) -> List.fold_left aux acc l
in aux [] g
let find_logic_info_decl li =
let module F = struct exception Found of global end in
let globs = (Ast.get()).globals in
try
List.iter
(fun g -> match g with
| GAnnot (ga,_) ->
if
List.exists
(fun li' -> Logic_info.equal li li')
(extract_logic_infos ga)
then raise (F.Found g)
| _ -> ())
globs;
Kernel.fatal "Reordering AST: unknown declaration \
for logic function or predicate %s"
li.l_var_info.lv_name
with F.Found g -> g
class reorder_ast: Visitor.frama_c_visitor =
let unique_name_recursive_axiomatic =
let i = ref 0 in
fun () ->
if !i = 0 then begin incr i; "__FC_recursive_axiomatic" end
else begin
let res = "__FC_recursive_axiomatic_" ^ (string_of_int !i) in
incr i; res
end
in
object(self)
inherit Visitor.frama_c_inplace
val mutable known_enuminfo = Enuminfo.Set.empty
val mutable known_compinfo = Compinfo.Set.empty
val mutable known_typeinfo = Typeinfo.Set.empty
val mutable known_var = Varinfo.Set.empty
val mutable known_logic_info = Logic_info.Set.empty
val mutable local_logic_info = Logic_info.Set.empty
(* globals that have to be declared before current declaration. *)
val mutable needed_decls = []
(* global annotations are treated separately, as they need special
care when revisiting their content *)
val mutable needed_annots = []
val current_annot = Stack.create ()
val subvisit = Stack.create ()
val typedefs = Stack.create ()
val logic_info_deps = Global_annotation_graph.create ()
method private add_known_enuminfo ei =
known_enuminfo <- Enuminfo.Set.add ei known_enuminfo
method private add_known_compinfo ci =
known_compinfo <- Compinfo.Set.add ci known_compinfo
method private add_known_type ty =
known_typeinfo <- Typeinfo.Set.add ty known_typeinfo
method private add_known_var vi =
known_var <- Varinfo.Set.add vi known_var
method private add_known_logic_info li =
known_logic_info <- Logic_info.Set.add li known_logic_info
method private add_needed_decl g =
needed_decls <- g :: needed_decls
method private add_needed_annot g =
needed_annots <- g :: needed_annots
method private add_annot_depend g =
try
let g' = Stack.top current_annot in
if g == g' then ()
else
Global_annotation_graph.add_edge
logic_info_deps g g' (* g' depends upon g *)
with Stack.Empty ->
Global_annotation_graph.add_vertex logic_info_deps g
(* Otherwise, if we only have one annotation to take care of,
the graph will be empty... *)
method private add_known_annots g =
let lis = extract_logic_infos g in
List.iter self#add_known_logic_info lis
method private clear_deps () =
needed_decls <- [];
needed_annots <- [];
Stack.clear current_annot;
Stack.clear typedefs;
Global_annotation_graph.clear logic_info_deps
method private make_annots g =
let g =
match g with
| [ g ] -> g
| _ -> (* We'll eventually add some globals, but the value returned
by visitor itself is supposed to be a singleton. Everything
is done in post-action.
*)
Kernel.fatal "unexpected result of visiting global when reordering"
in
let deps =
if Global_annotation_graph.nb_vertex logic_info_deps = 0 then []
else if Global_annotation_graph.has_cycle logic_info_deps then begin
(* Assumption: elements from the stdlib are not part of a cycle with
others logic functions, i.e. the stdlib is well-formed. *)
let entries =
Global_annotation_graph.fold
(fun g acc ->
let stdlib =
Cil.findAttribute "fc_stdlib" (Cil_datatype.Global.attr g)
in
let key =
match stdlib with
| [ AStr s ] -> s
| _ -> ""
in
let elts =
try Datatype.String.Map.find key acc
with Not_found -> []
in
Datatype.String.Map.add key (g::elts) acc
)
logic_info_deps Datatype.String.Map.empty
in
Datatype.String.Map.fold
(fun k l res ->
let attr = if k = "" then [] else [ Attr("fc_stdlib", [AStr k])] in
let entries =
List.fold_left
(fun acc g ->
match g with GAnnot (g,_) -> g :: acc | _ -> acc)
[] l
in
(GAnnot
(Daxiomatic
(unique_name_recursive_axiomatic (),
entries, attr,
Location.unknown),
Location.unknown))::res)
entries []
end else begin
Global_annotation_graph.fold
(fun ga acc -> ga :: acc) logic_info_deps []
end
in
assert (List.length deps = List.length needed_annots);
match g with
| GAnnot _ -> List.rev deps
(** g is already in the dependencies graph. *)
| _ -> List.rev (g::deps)
(* TODO: add methods for uses of undeclared identifiers.
Use functions that maps an identifier to its decl.
Don't forget to check for cycles for TNamed and logic_info.
*)
method! vtype ty =
(match ty with
| TVoid _ | TInt _ | TFloat _ | TPtr _
| TFun _ | TBuiltin_va_list _ | TArray _ -> ()
| TNamed (ty,_) ->
let g = find_typeinfo ty in
if not (Typeinfo.Set.mem ty known_typeinfo) then begin
self#add_needed_decl g;
Stack.push g typedefs;
Stack.push true subvisit;
ignore
(Visitor.visitFramacGlobal (self:>Visitor.frama_c_visitor) g);
ignore (Stack.pop typedefs);
ignore (Stack.pop subvisit);
end
else
Stack.iter
(fun g' -> if g == g' then
Kernel.fatal
"Globals' reordering failed: \
recursive definition of type %s"
ty.tname)
typedefs
| TComp(ci,_,_) ->
if not (Compinfo.Set.mem ci known_compinfo) then begin
self#add_needed_decl (GCompTagDecl (ci,Location.unknown));
self#add_known_compinfo ci
end
| TEnum(ei,_) ->
if not (Enuminfo.Set.mem ei known_enuminfo) then begin
self#add_needed_decl (GEnumTagDecl (ei, Location.unknown));
self#add_known_enuminfo ei
end);
DoChildren
method! vvrbl vi =
if vi.vglob && not (Varinfo.Set.mem vi known_var) then begin
if Cil.isFunctionType vi.vtype then
self#add_needed_decl (GFunDecl (Cil.empty_funspec(),vi,vi.vdecl))
else
self#add_needed_decl (GVarDecl (vi,vi.vdecl));
self#add_known_var vi;
end;
DoChildren
method private logic_info_occurrence lv =
if not (Logic_env.is_builtin_logic_function lv.l_var_info.lv_name) then
begin
let g = find_logic_info_decl lv in
if not (Logic_info.Set.mem lv known_logic_info) then begin
self#add_annot_depend g;
Stack.push true subvisit;
(* visit will also push g in needed_annot. *)
ignore (Visitor.visitFramacGlobal (self:>Visitor.frama_c_visitor) g);
ignore (Stack.pop subvisit)
end else if List.memq g needed_annots then begin
self#add_annot_depend g;
end;
end
method private add_local_logic_info li =
local_logic_info <- Logic_info.Set.add li local_logic_info
method private remove_local_logic_info li =
local_logic_info <- Logic_info.Set.remove li local_logic_info
method private is_local_logic_info li =
Logic_info.Set.mem li local_logic_info
method! vlogic_var_use lv =
let logic_infos = Annotations.logic_info_of_global lv.lv_name in
(try
self#logic_info_occurrence
(List.find
(fun x -> Cil_datatype.Logic_var.equal x.l_var_info lv)
logic_infos)
with Not_found -> ());
DoChildren
method! vterm t =
match t.term_node with
| Tlet(li,_) -> self#add_local_logic_info li;
DoChildrenPost (fun t -> self#remove_local_logic_info li; t)
| _ -> DoChildren
method! vpredicate_node p =
match p with
| Plet(li,_) -> self#add_local_logic_info li;
DoChildrenPost (fun t -> self#remove_local_logic_info li; t)
| _ -> DoChildren
method! vlogic_info_use lv =
if not (self#is_local_logic_info lv) then self#logic_info_occurrence lv;
DoChildren
method! vglob_aux g =
let is_subvisit = try Stack.top subvisit with Stack.Empty -> false in
(match g with
| GType (ty,_) -> self#add_known_type ty; self#add_needed_decl g
| GCompTagDecl(ci,_) | GCompTag(ci,_) -> self#add_known_compinfo ci
| GEnumTagDecl(ei,_) | GEnumTag(ei,_) -> self#add_known_enuminfo ei
| GVarDecl(vi,_) | GVar (vi,_,_) | GFun({svar = vi},_) | GFunDecl (_,vi,_)
-> self#add_known_var vi
| GAsm _ | GPragma _ | GText _ -> ()
| GAnnot (ga,_) ->
Stack.push g current_annot;
self#add_known_annots ga;
Global_annotation_graph.add_vertex logic_info_deps g;
self#add_needed_annot g);
let post_action g =
(match g with
| [GAnnot _] -> ignore (Stack.pop current_annot)
| _ -> ());
if is_subvisit then g (* everything will be done at toplevel *)
else begin
let res = List.rev_append needed_decls (self#make_annots g) in
self#clear_deps ();
res
end
in
DoChildrenPost post_action
end
module Remove_spurious = struct
type env =
{ typeinfos: Typeinfo.Set.t;
compinfos: Compinfo.Set.t;
enuminfos: Enuminfo.Set.t;
varinfos: Varinfo.Set.t;
logic_infos: Logic_info.Set.t;
kept: global list;
}
let treat_one_global acc g =
match g with
| GType (ty,_) when Typeinfo.Set.mem ty acc.typeinfos -> acc
| GType (ty,_) ->
{ acc with
typeinfos = Typeinfo.Set.add ty acc.typeinfos;
kept = g :: acc.kept }
| GCompTag _ -> { acc with kept = g :: acc.kept }
| GCompTagDecl(ci,_) when Compinfo.Set.mem ci acc.compinfos -> acc
| GCompTagDecl(ci,_) ->
{ acc with
compinfos = Compinfo.Set.add ci acc.compinfos;
kept = g :: acc.kept }
| GEnumTag _ -> { acc with kept = g :: acc.kept }
| GEnumTagDecl(ei,_) when Enuminfo.Set.mem ei acc.enuminfos -> acc
| GEnumTagDecl(ei,_) ->
{ acc with
enuminfos = Enuminfo.Set.add ei acc.enuminfos;
kept = g :: acc.kept }
| GVarDecl(vi,_) | GFunDecl (_, vi, _)
when Varinfo.Set.mem vi acc.varinfos -> acc
| GVarDecl(vi,_) ->
{ acc with
varinfos = Varinfo.Set.add vi acc.varinfos;
kept = g :: acc.kept }
| GVar _ | GFun _ | GFunDecl _ -> { acc with kept = g :: acc.kept }
| GAsm _ | GPragma _ | GText _ -> { acc with kept = g :: acc.kept }
| GAnnot (a,_) ->
let lis = extract_logic_infos a in
if List.exists (fun x -> Logic_info.Set.mem x acc.logic_infos) lis
then acc
else begin
let known_li =
List.fold_left (Extlib.swap Logic_info.Set.add) acc.logic_infos lis
in
{ acc with
kept = g::acc.kept;
logic_infos = known_li;
}
end
let empty =
{ typeinfos = Typeinfo.Set.empty;
compinfos = Compinfo.Set.empty;
enuminfos = Enuminfo.Set.empty;
varinfos = Varinfo.Set.empty;
logic_infos = Logic_info.Set.empty;
kept = [];
}
let process file =
let env = List.fold_left treat_one_global empty file.globals in
file.globals <- List.rev env.kept
end
let reorder_custom_ast ast =
Visitor.visitFramacFile (new reorder_ast) ast;
Remove_spurious.process ast
let reorder_ast () = reorder_custom_ast (Ast.get())
(* Fill logic tables with builtins *)
let init_cil () =
Cil.initCIL (Logic_builtin.init()) (get_machdep ());
Logic_env.Builtins.apply ();
Logic_env.prepare_tables ()
let re_included_file = Str.regexp "^[.]+ \\(.*\\)$"
let file_hash file =
Digest.to_hex (Digest.file file)
let add_source_if_new tbl (fp : Filepath.Normalized.t) =
if not (Hashtbl.mem tbl fp) then
Hashtbl.replace tbl fp (file_hash (fp:>string))
(* Inserts, into the hashtbl of (Filepath.Normalized.t, Digest.t), [tbl],
the included sources listed in [file],
which contains the output of 'gcc -H -MM'. *)
let add_included_sources tbl file =
let ic = open_in file in
try
while true; do
let line = input_line ic in
if Str.string_match re_included_file line 0 then
let f = Str.matched_group 1 line in
add_source_if_new tbl (Filepath.Normalized.of_string f)
done;
assert false
with End_of_file ->
close_in ic
let print_all_sources out all_sources_tbl =
let elems =
Hashtbl.fold (fun f hash acc ->
(Filepath.Normalized.to_pretty_string f, hash) :: acc)
all_sources_tbl []
in
let sorted_elems =
List.sort (fun (f1, _) (f2, _) -> Extlib.compare_ignore_case f1 f2) elems
in
if Filepath.Normalized.is_special_stdout out then begin
(* text format, to stdout *)
Kernel.feedback "Audit: all used sources, with md5 hashes:@\n%a"
(Pretty_utils.pp_list ~sep:"@\n"
(Pretty_utils.pp_pair ~sep:": "
Format.pp_print_string Format.pp_print_string)) sorted_elems
end else begin
(* json format, into file [out] *)
let json =
`Assoc
[("sources",
`Assoc (List.map (fun (f, hash) -> f, `String hash) sorted_elems)
)]
in
Json.merge_object out json
end
let compute_sources_table cpp_commands =
let all_sources_tbl = Hashtbl.create 7 in
let process_file (file, cmd_opt) =
add_source_if_new all_sources_tbl (get_filepath file);
match cmd_opt with
| None -> ()
| Some (cpp_cmd, _ppf, _sl) ->
let tmp_file = create_temp_file "audit_produce_sources" ".txt" in
let tmp_file = (tmp_file :> string) in
let cmd_for_sources = cpp_cmd ^ " -H -MM >/dev/null 2>" ^ tmp_file in
let exit_code = Sys.command cmd_for_sources in
if exit_code = 0
then add_included_sources all_sources_tbl tmp_file
else
let cause_frama_c_compliant =
if Kernel.CppGnuLike.get () then "" else
Format.asprintf
"\nPlease ensure preprocessor is Frama-C compliant \
(see option %s)" Kernel.CppGnuLike.option_name
in
Kernel.abort "error running command to obtain included sources \
(exit code %d):@\n%s%s"
exit_code cmd_for_sources cause_frama_c_compliant;
in
List.iter process_file cpp_commands;
all_sources_tbl
let source_hashes_of_json path =
try
let json = Json.from_file path in
let open Yojson.Basic.Util in
json |> member "sources" |> to_assoc |>
List.map (fun (k, h) -> k, to_string h)
with
| Yojson.Json_error msg ->
Kernel.abort "error reading %a: %s"
Filepath.Normalized.pretty path msg
| Yojson.Basic.Util.Type_error (msg, v) ->
Kernel.abort "error reading %a: %s - %s"
Filepath.Normalized.pretty path msg
(Yojson.Basic.pretty_to_string v)
let check_source_hashes expected actual_table =
let checked, diffs =
Hashtbl.fold (fun fp hash (acc_checked, acc_diffs) ->
let fp = Filepath.Normalized.to_pretty_string fp in
let expected_hash = List.assoc_opt fp expected in
let checked = Datatype.String.Set.add fp acc_checked in
let diffs =
if Some hash = expected_hash then acc_diffs
else (fp, hash, expected_hash) :: acc_diffs
in
checked, diffs
) actual_table (Datatype.String.Set.empty, [])
in
if diffs <> [] then begin
let diffs =
List.sort (fun (fp1, _, _) (fp2, _, _) ->
Extlib.compare_ignore_case fp1 fp2) diffs
in
List.iter (fun (fp, got, expected) ->
Kernel.warning ~wkey:Kernel.wkey_audit
"different hashes for %s: got %s, expected %s"
fp got (Option.value ~default:("<none> (not in list)") expected)
) diffs
end;
let expected_names = List.map fst expected in
let missing =
List.filter (fun fp -> not (Datatype.String.Set.mem fp checked))
expected_names
in
if missing <> [] then begin
let missing = List.sort Extlib.compare_ignore_case missing in
Kernel.warning ~wkey:Kernel.wkey_audit
"missing files:@\n%a"
(Pretty_utils.pp_list ~sep:"@\n" Format.pp_print_string) missing
end
let print_and_exit cpp_commands =
let print_cpp_cmd (cpp_cmd, _ppf, _) =
Kernel.result "Preprocessing command:@.%s" cpp_cmd
in
List.iter (fun (_f, ocmd) -> Option.iter print_cpp_cmd ocmd) cpp_commands;
raise Cmdline.Exit
let prepare_from_c_files () =
init_cil ();
let files = Files.get () in (* Allow pre-registration of prolog files *)
let cpp_commands = List.map (fun f -> (f, build_cpp_cmd f)) files in
if Kernel.PrintCppCommands.get () then print_and_exit cpp_commands;
let audit_check_path = Kernel.AuditCheck.get () in
if not (Filepath.Normalized.is_empty audit_check_path) then begin
let all_sources_tbl = compute_sources_table cpp_commands in
let expected_hashes = source_hashes_of_json audit_check_path in
check_source_hashes expected_hashes all_sources_tbl
end;
let audit_path = Kernel.AuditPrepare.get () in
if not (Filepath.Normalized.is_empty audit_path) then begin
let all_sources_tbl = compute_sources_table cpp_commands in
print_all_sources audit_path all_sources_tbl;
if not (Filepath.Normalized.is_special_stdout audit_path) then
Kernel.feedback "Audit: sources list written to: %a@."
Filepath.Normalized.pretty audit_path;
end;
let cil, cabs_files = files_to_cabs_cil files cpp_commands in
prepare_cil_file cil;
(* prepare_cil_file may call syntactic transformers, that will ultimately
reset the untyped AST. Restore it here. *)
Ast.UntypedFiles.set cabs_files
let init_project_from_visitor ?(reorder=false) prj
(vis:Visitor.frama_c_visitor) =
if not (Visitor_behavior.is_copy vis#behavior)
|| not (Project.equal prj (Option.get vis#project))
then
Kernel.fatal
"Visitor does not copy or does not operate on correct project.";
Project.on prj init_cil ();
let old_ast = Ast.get () in
let ast = visitFramacFileCopy vis old_ast in
let finalize ast =
computeCFG ~clear_id:false ast;
Ast.set_file ast
in
let selection = State_selection.with_dependencies Ast.self in
Project.on ~selection prj finalize ast;
(* reorder _before_ check. *)
if reorder then Project.on prj reorder_ast ();
if Kernel.Check.get() then begin
let name = prj.Project.name in
Project.on prj (Filecheck.check_ast ~ast) ("AST of " ^ name);
assert
(Kernel.verify (old_ast == Ast.get())
"Creation of project %s modifies original project" name);
Filecheck.check_ast ("Original AST after creation of " ^ name)
end
let prepare_from_visitor ?reorder prj visitor =
let visitor = visitor prj in
init_project_from_visitor ?reorder prj visitor
let create_project_from_visitor ?reorder ?(last=true) prj_name visitor =
let selection =
State_selection.list_union
(List.map State_selection.with_dependencies
[ Kernel.Files.self; Files.pre_register_state ])
in
let selection = State_selection.diff State_selection.full selection in
let prj = Project.create_by_copy ~selection ~last prj_name in
(* reset projectified parameters to their default values *)
let temp = Project.create "File.temp" in
Project.copy
~selection:(Parameter_state.get_reset_selection ()) ~src:temp prj;
Project.remove ~project:temp ();
prepare_from_visitor ?reorder prj visitor;
prj
let init_from_c_files files =
(match files with [] -> () | _ :: _ -> Files.register files);
prepare_from_c_files ()
let init_from_cmdline () =
let prj1 = Project.current () in
if Kernel.Copy.get () then begin
let selection =
State_selection.list_union
(List.map State_selection.with_dependencies
[ Cil_builtins.Builtin_functions.self;
Logic_env.Logic_info.self;
Logic_env.Logic_type_info.self;
Logic_env.Logic_ctor_info.self;
Ast.self ])
in
Project.clear ~selection ();
let prj2 = Project.create_by_copy ~last:false "debug_copy_prj" in
Project.set_current prj2;
end;
let files = Kernel.Files.get () in
if files = [] && not !Fc_config.is_gui then Kernel.warning "no input file.";
let files = List.map (fun f -> from_filename f) files in
try
init_from_c_files files;
if Kernel.Check.get () then begin
Filecheck.check_ast "Copy of original AST"
end;
if Kernel.Copy.get () then begin
Project.on prj1 fill_built_ins ();
prepare_from_visitor prj1 (fun prj -> new Visitor.frama_c_copy prj);
Project.set_current prj1;
end;
with Ast.Bad_Initialization s ->
Kernel.fatal "@[<v 0>Cannot initialize from C files@ \
Kernel raised Bad_Initialization %s@]" s
let init_from_cmdline =
Journal.register
"File.init_from_cmdline"
(Datatype.func Datatype.unit Datatype.unit)
init_from_cmdline
let init_from_c_files =
Journal.register
"File.init_from_c_files"
(Datatype.func (Datatype.list ty) Datatype.unit)
init_from_c_files
let prepare_from_c_files =
Journal.register
"File.prepare_from_c_files"
(Datatype.func Datatype.unit Datatype.unit)
prepare_from_c_files
let () = Ast.set_default_initialization
(fun () ->
if Files.is_computed () then prepare_from_c_files ()
else init_from_cmdline ())
let pp_file_to fmt_opt =
let pp_ast = Printer.pp_file in
let ast = Ast.get () in
(match fmt_opt with
| None -> Kernel.CodeOutput.output (fun fmt -> pp_ast fmt ast)
| Some fmt -> pp_ast fmt ast)
let unjournalized_pretty prj (fmt_opt:Format.formatter option) () =
Project.on prj pp_file_to fmt_opt
let journalized_pretty_ast =
Journal.register "File.pretty_ast"
(Datatype.func3
~label1:("prj",Some Project.current) Project.ty
~label2:("fmt",Some (fun () -> None))
(let module O = Datatype.Option(Datatype.Formatter) in
O.ty)
Datatype.unit Datatype.unit)
unjournalized_pretty
let pretty_ast ?(prj=Project.current ()) ?fmt () =
journalized_pretty_ast prj fmt ()
let create_rebuilt_project_from_visitor
?reorder ?last ?(preprocess=false) prj_name visitor =
let prj = create_project_from_visitor ?reorder ?last prj_name visitor in
try
let f =
let name = "frama_c_project_" ^ prj_name ^ "_" in
let ext = if preprocess then ".c" else ".i" in
let debug = Kernel.Debug.get () > 0 in
create_temp_file ~debug name ext
in
let cout = open_out (f :> string) in
let fmt = Format.formatter_of_out_channel cout in
unjournalized_pretty prj (Some fmt) ();
let redo () =
(* Kernel.feedback "redoing initialization on file %s" f;*)
Files.reset ();
init_from_c_files [ if preprocess then from_filename f else NoCPP f ]
in
Project.on prj redo ();
prj
with Sys_error s ->
Kernel.abort "cannot create temporary file: %s" s
(*
Local Variables:
compile-command: "make -C ../../.."
End:
*)