Revamped Timeout module to save and restore seconds and symops.

Summary:public
Revamped Timeout module by storing elapsed wallclock seconds, and the status of symops, in case of recursive calls.
Extended the API with suspend() and resume() to pause and resume the current timeout.
These are used before and after an on-demand call to the analysis functions.
This achieves the effect that each procedure, even though is interrupted, has its own time and symop counters, which are suspended and resumed as required.

Reviewed By: jeremydubreil

Differential Revision: D2976918

fb-gh-sync-id: 0ed1079
shipit-source-id: 0ed1079
master
Cristiano Calcagno 9 years ago committed by Facebook Github Bot 1
parent 2277c23c60
commit f3ba6c3906

@ -52,7 +52,8 @@ let abs_source_file_from_path fname =
(** convert a project root directory and a full path to a rooted source file *)
let rel_source_file_from_abs_path root fname =
let relative_complemented_fname = filename_to_relative root fname in
if Utils.string_is_prefix root fname && Filename.is_relative relative_complemented_fname then
if string_is_prefix root fname &&
Filename.is_relative relative_complemented_fname then
Relative relative_complemented_fname
else
(* The project root is not a prefix of the file name *)

@ -936,7 +936,7 @@ let save_source_files cfg =
>
DB.file_modified_time dest_file) in
if needs_copy then
match Utils.copy_file source_file_str dest_file_str with
match copy_file source_file_str dest_file_str with
| Some _ -> ()
| None -> L.err "Error cannot create copy of source file %s@." source_file_str in
Node.iter_proc_desc cfg process_proc

@ -180,7 +180,7 @@ module Node : sig
val get_distance_to_exit: t -> int option
(** Return a description of the node *)
val get_description : Utils.printenv -> t -> string
val get_description : printenv -> t -> string
(** Get the exception nodes from the current node *)
val get_exn : t -> t list

@ -92,9 +92,10 @@ val handle_exception : exn -> bool
(** print a description of the exception to the html output *)
val print_exception_html : string -> exn -> unit
(** pretty print an error given its (id,key), location, kind, name, description, and optional ml location *)
(** pretty print an error given its (id,key), location, kind, name, description,
and optional ml location *)
val pp_err : int * int -> Location.t -> err_kind -> Localise.t -> Localise.error_desc ->
Utils.ml_loc option -> Format.formatter -> unit -> unit
ml_loc option -> Format.formatter -> unit -> unit
(** Turn an exception into an error name, error description,
location in ml source, and category *)

@ -251,79 +251,6 @@ let filter_max exe_env filter set priority_set =
find_max 1 filter (WeightedPnameSet.inter set priority_set)
with Not_found -> find_max 1 filter set
(** Handle timeout events *)
module Timeout : sig
(** execute the function up to a given timeout given by the parameter *)
val exe_timeout : int -> ('a -> unit) -> 'a -> failure_kind option
end = struct
let set_alarm nsecs =
match Config.os_type with
| Config.Unix | Config.Cygwin ->
ignore (Unix.setitimer Unix.ITIMER_REAL
{ Unix.it_interval = 3.0; (* try again after 3 seconds if the signal is lost *)
Unix.it_value = float_of_int nsecs })
| Config.Win32 ->
SymOp.set_wallclock_alarm nsecs
let unset_alarm () =
match Config.os_type with
| Config.Unix | Config.Cygwin -> set_alarm 0
| Config.Win32 -> SymOp.unset_wallclock_alarm ()
let timeout_action _ =
unset_alarm ();
raise (Analysis_failure_exe (FKtimeout))
let () = begin
match Config.os_type with
| Config.Unix | Config.Cygwin ->
Sys.set_signal Sys.sigvtalrm (Sys.Signal_handle timeout_action);
Sys.set_signal Sys.sigalrm (Sys.Signal_handle timeout_action)
| Config.Win32 ->
SymOp.set_wallclock_timeout_handler timeout_action;
(* use the Gc alarm for periodic timeout checks *)
ignore (Gc.create_alarm SymOp.check_wallclock_alarm)
end
let current_timeouts = ref []
let exe_timeout iterations f x =
let restore_timeout () =
match !current_timeouts with
| prev_iterations :: _ ->
set_alarm prev_iterations
| [] ->
() in
let unwind () =
let pop () = match !current_timeouts with
| _ :: l -> current_timeouts := l
| [] -> () in
unset_alarm ();
SymOp.unset_alarm ();
pop () in
let before () =
current_timeouts := iterations :: !current_timeouts;
set_iterations iterations;
set_alarm (get_timeout_seconds ());
SymOp.set_alarm () in
let after () =
unwind ();
restore_timeout () in
try
before ();
f x;
after ();
None
with
| Analysis_failure_exe kind ->
after ();
Errdesc.warning_err (State.get_loc ()) "TIMEOUT: %a@." pp_failure_kind kind;
Some kind
| exe ->
after ();
raise exe
end
(** Main algorithm responsible for driving the analysis of an Exe_env (set of procedures).
The algorithm computes dependencies between procedures,
@ -341,7 +268,6 @@ let main_algorithm exe_env analyze_proc filter_out process_result : unit =
WeightedPnameSet.filter (fun (n, _) -> address_taken_of n) !G.wpnames_todo in
G.tot_procs := WeightedPnameSet.cardinal !G.wpnames_todo;
G.num_procs_done := 0;
let max_timeout = ref 1 in
let wpname_can_be_analyzed (pname, _) : bool =
(* Return true if [pname] is not up to date and it can be analyzed right now *)
Procname.Set.for_all
@ -365,7 +291,6 @@ let main_algorithm exe_env analyze_proc filter_out process_result : unit =
post_process_procs exe_env [pname]
else
begin
max_timeout := max (Specs.get_iterations pname) !max_timeout;
Specs.update_dependency_map pname;
process_result exe_env elem (analyze_proc exe_env pname);
end in

@ -10,15 +10,6 @@
(** Implementation of the Interprocedural Footprint Analysis Algorithm *)
(** Handle timeout events *)
module Timeout : sig
(** execute the function up to a given timeout given by the iterations parameter *)
val exe_timeout : int -> ('a -> unit) -> 'a -> Utils.failure_kind option
end
(** {2 Algorithm} *)
val procs_become_done : Cg.t -> Procname.t -> Procname.t list
val post_process_procs : Exe_env.t -> Procname.t list -> unit

@ -156,7 +156,7 @@ let arg_desc =
"analyze files captured since the last analysis plus any dependencies"
;
"-iterations",
Arg.Set_int iterations_cmdline,
Arg.Int set_iterations,
Some "n",
"set the max number of operations for each function, \
expressed as a multiple of symbolic operations (default n=1)"
@ -281,7 +281,7 @@ let arg_desc =
"set the max number of procedures in each cluster (default n=2000)"
;
"-seconds_per_iteration",
Arg.Set_int seconds_per_iteration,
Arg.Set_float seconds_per_iteration,
Some "n",
"set the number of seconds per iteration (default n=30)"
;
@ -890,7 +890,7 @@ let () =
else
let filter source_dir =
let source_dir_base = Filename.basename (DB.source_dir_to_string source_dir) in
IList.exists (fun s -> Utils.string_is_prefix s source_dir_base) !only_files_cmdline in
IList.exists (fun s -> string_is_prefix s source_dir_base) !only_files_cmdline in
IList.filter filter (DB.find_source_dirs ()) in
L.err "Found %d source files in %s@." (IList.length source_dirs) !Config.results_dir;

@ -85,13 +85,13 @@ module FileContainsStringMatcher = struct
try
DB.SourceFileMap.find source_file !source_map
with Not_found ->
try
let file_in = open_in (DB.source_file_to_string source_file) in
let pattern_found = file_contains regexp file_in in
close_in file_in;
source_map := DB.SourceFileMap.add source_file pattern_found !source_map;
pattern_found
with Sys_error _ -> false
try
let file_in = open_in (DB.source_file_to_string source_file) in
let pattern_found = file_contains regexp file_in in
close_in file_in;
source_map := DB.SourceFileMap.add source_file pattern_found !source_map;
pattern_found
with Sys_error _ -> false
end
module type MATCHABLE_JSON = sig
@ -358,20 +358,20 @@ let create_filters analyzer =
let test () =
Config.project_root := Some (Sys.getcwd ());
let filters =
IList.map (fun analyzer -> (analyzer, create_filters analyzer)) Utils.analyzers in
IList.map (fun analyzer -> (analyzer, create_filters analyzer)) analyzers in
let matching_analyzers path =
IList.fold_left
(fun l (a, f) -> if f.path_filter path then a:: l else l)
[] filters in
Utils.directory_iter
directory_iter
(fun path ->
if DB.is_source_file path then
let source_file = (DB.source_file_from_string path) in
let matching = matching_analyzers source_file in
if matching <> [] then
let matching_s =
Utils.join_strings ", "
(IList.map Utils.string_of_analyzer matching) in
join_strings ", "
(IList.map string_of_analyzer matching) in
Logging.stderr "%s -> {%s}@."
(DB.source_file_to_rel_path source_file)
matching_s)

@ -34,7 +34,7 @@ type filters =
val do_not_filter : filters
(** Create filters based on the config file *)
val create_filters : Utils.analyzer -> filters
val create_filters : analyzer -> filters
module NeverReturnNull : sig
type matcher = DB.source_file -> Procname.t -> bool

@ -457,7 +457,7 @@ module ProcsCsv = struct
pp "%s," sv.vrank;
pp "%d," sv.vin_calls;
pp "%d," sv.vout_calls;
pp "%s," sv.vproof_trace;
pp "%s@\n" sv.vproof_trace;
end
module ProcsXml = struct
@ -540,7 +540,7 @@ module BugsCsv = struct
let qualifier_tag_xml =
let xml_node = Io_infer.Xml.create_tree Io_infer.Xml.tag_qualifier_tags [] (error_desc_to_xml_tags error_desc) in
let p fmt () = F.fprintf fmt "%a" (Io_infer.Xml.pp_document false) xml_node in
let s = Utils.pp_to_string p () in
let s = pp_to_string p () in
Escape.escape_csv s in
let kind = Exceptions.err_kind_string ekind in
let type_str = Localise.to_string error_name in

@ -669,9 +669,7 @@ let extract_specs tenv pdesc pathset : Prop.normal Specs.spec list =
let pplist = Paths.PathSet.elements pathset in
let f (prop, path) =
let _, prop' = Cfg.remove_locals_formals pdesc prop in
(* let () = L.out "@.BEFORE abs:@.$%a@." (Prop.pp_prop Utils.pe_text) prop' in *)
let prop'' = Abs.abstract pname tenv prop' in
(* let () = L.out "@.AFTER abs:@.$%a@." (Prop.pp_prop Utils.pe_text) prop'' in *)
let pre, post = Prop.extract_spec prop'' in
let pre' = Prop.normalize (Prop.prop_sub sub pre) in
if !Config.curr_language = Config.Java && Cfg.Procdesc.get_access pdesc <> Sil.Private then
@ -1057,7 +1055,7 @@ let report_runtime_exceptions tenv pdesc summary =
let report (pre, runtime_exception) =
if should_report pre then
let pre_str =
Utils.pp_to_string (Prop.pp_prop pe_text) (Specs.Jprop.to_prop pre) in
pp_to_string (Prop.pp_prop pe_text) (Specs.Jprop.to_prop pre) in
let exn_desc = Localise.java_unchecked_exn_desc pname runtime_exception pre_str in
let exn = Exceptions.Java_runtime_exception (runtime_exception, pre_str, exn_desc) in
Reporting.log_error pname ~pre: (Some (Specs.Jprop.to_prop pre)) exn in
@ -1116,7 +1114,7 @@ let analyze_proc exe_env (proc_name: Procname.t) : Specs.summary =
| None -> assert false in
reset_global_values proc_desc;
let go, get_results = perform_analysis_phase cfg tenv proc_name proc_desc in
let res = Fork.Timeout.exe_timeout (Specs.get_iterations proc_name) go () in
let res = Timeout.exe_timeout go () in
let specs, phase = get_results () in
let elapsed = Unix.gettimeofday () -. init_time in
let prev_summary = Specs.get_summary_unsafe "analyze_proc" proc_name in

@ -11,24 +11,51 @@
(** Module to handle IO. Includes html and xml modules. *)
module Html : sig
val close : Unix.file_descr * Format.formatter -> unit (** Close an Html file *)
val create : DB.Results_dir.path_kind -> DB.Results_dir.path -> Unix.file_descr * Format.formatter (** Create a new html file *)
val modified_during_analysis : DB.Results_dir.path -> bool (** Return true if the html file was modified since the beginning of the analysis *)
val open_out : DB.Results_dir.path -> Unix.file_descr * Format.formatter (** Open an Html file to append data *)
val pp_line_link : ?with_name: bool -> ?text: (string option) -> DB.Results_dir.path -> Format.formatter -> int -> unit (** Print an html link to the given line number of the current source file *)
val pp_hline : Format.formatter -> unit -> unit (** Print a horizontal line *)
val pp_end_color : Format.formatter -> unit -> unit (** Print end color *)
(** Close an Html file *)
val close : Unix.file_descr * Format.formatter -> unit
(** [pp_node_link path_to_root description isvisited isproof fmt id] prints an html link to the given node.
(** Create a new html file *)
val create :
DB.Results_dir.path_kind -> DB.Results_dir.path -> Unix.file_descr * Format.formatter
(** Return true if the html file was modified since the beginning of the analysis *)
val modified_during_analysis : DB.Results_dir.path -> bool
(** Open an Html file to append data *)
val open_out : DB.Results_dir.path -> Unix.file_descr * Format.formatter
(** Print an html link to the given line number of the current source file *)
val pp_line_link :
?with_name: bool -> ?text: (string option) ->
DB.Results_dir.path -> Format.formatter -> int -> unit
(** Print a horizontal line *)
val pp_hline : Format.formatter -> unit -> unit
(** Print end color *)
val pp_end_color : Format.formatter -> unit -> unit
(** [pp_node_link path_to_root description isvisited isproof fmt id]
prints an html link to the given node.
[path_to_root] is the path to the dir for the procedure in the spec db.
[description] is a string description.
[is_visited] indicates whether the node should be active or greyed out.
[is_proof] indicates whether the node is part of a proof and should be green.
[id] is the node identifier. *)
val pp_node_link : DB.Results_dir.path -> string -> int list -> int list -> int list -> bool -> bool -> Format.formatter -> int -> unit
val pp_proc_link : DB.Results_dir.path -> Procname.t -> Format.formatter -> string -> unit (** Print an html link to the given proc *)
val pp_session_link : ?with_name: bool -> string list -> Format.formatter -> int * int * int -> unit (** Print an html link given node id and session *)
val pp_start_color : Format.formatter -> Utils.color -> unit (** Print start color *)
val pp_node_link :
DB.Results_dir.path -> string -> int list -> int list -> int list ->
bool -> bool -> Format.formatter -> int -> unit
(** Print an html link to the given proc *)
val pp_proc_link :
DB.Results_dir.path -> Procname.t -> Format.formatter -> string -> unit
(** Print an html link given node id and session *)
val pp_session_link :
?with_name: bool -> string list -> Format.formatter -> int * int * int -> unit
(** Print start color *)
val pp_start_color : Format.formatter -> color -> unit
end
(** Create and print xml trees *)

@ -20,7 +20,7 @@ val convert_string : string -> string
val pp_string : style -> Format.formatter -> string -> unit
(** Print color command *)
val pp_color : Format.formatter -> Utils.color -> unit
val pp_color : Format.formatter -> color -> unit
(** Prelude for a latex file with the given author and title and table of contents flag *)
val pp_begin : Format.formatter -> (string * string * bool) -> unit

@ -15,8 +15,8 @@ module F = Format
let trace () = Config.from_env_variable "INFER_TRACE_ONDEMAND"
let one_cluster_per_procedure () = false
let () = Config.ondemand_enabled :=
Config.from_env_variable "INFER_ONDEMAND"
let () =
Config.ondemand_enabled := true
let across_files () = true
@ -27,7 +27,7 @@ let ondemand_file () = Config.get_env_variable "INFER_ONDEMAND_FILE"
let read_dirs_to_analyze () =
let lines_opt = match ondemand_file () with
| None -> None
| Some fname -> Utils.read_file fname in
| Some fname ->read_file fname in
match lines_opt with
| None ->
None
@ -98,6 +98,7 @@ type global_state =
}
let save_global_state () =
Timeout.suspend_existing_timeout ();
{
abs_val = !Config.abs_val;
abstraction_rules = Abs.get_current_rules ();
@ -117,7 +118,8 @@ let restore_global_state st =
Config.footprint := st.footprint_mode;
Printer.html_formatter := st.html_formatter;
Ident.NameGenerator.set_current st.name_generator;
State.restore_state st.symexec_state
State.restore_state st.symexec_state;
Timeout.resume_previous_timeout ()
let do_analysis curr_pdesc callee_pname =
let curr_pname = Cfg.Procdesc.get_proc_name curr_pdesc in
@ -164,9 +166,10 @@ let do_analysis curr_pdesc callee_pname =
postprocess ();
restore_global_state old_state;
with e ->
L.stderr "ONDEMAND EXCEPTION %a %s %s@."
L.stderr "@.ONDEMAND EXCEPTION %a %s@.@.CALL STACK@.%s@.BACK TRACE@.%s@."
Procname.pp callee_pname
(Printexc.to_string e)
(Printexc.raw_backtrace_to_string (Printexc.get_callstack 1000))
(Printexc.get_backtrace ());
restore_global_state old_state;
raise e in

@ -369,7 +369,8 @@ let java_is_vararg = function
let is_constructor = function
| Java_method js -> js.method_name = "<init>"
| ObjC_Cpp_method name ->
(name.method_name = "new") || Utils.string_is_prefix "init" name.method_name
(name.method_name = "new") ||
string_is_prefix "init" name.method_name
| _ -> false
(** [is_objc_dealloc pname] returns true if [pname] is the dealloc method in Objective-C *)

@ -31,39 +31,47 @@ val edge_get_source : edge -> node
(** Return the successor nodes of the edge *)
val edge_get_succs : edge -> node list
(** [edge_from_source g n footprint_part is_hpred] finds and edge with the given source [n] in prop [g].
[footprint_part] indicates whether to search the edge in the footprint part, and [is_pred] whether it is an hpred edge. *)
(** [edge_from_source g n footprint_part is_hpred] finds and edge
with the given source [n] in prop [g].
[footprint_part] indicates whether to search the edge in the footprint part,
and [is_pred] whether it is an hpred edge. *)
val edge_from_source : t -> node -> bool -> bool -> edge option
(** [get_succs g n footprint_part is_hpred] returns the successor nodes of [n] in [g].
[footprint_part] indicates whether to search the successors in the footprint part, and [is_pred] whether to follow hpred edges. *)
[footprint_part] indicates whether to search the successors in the footprint part,
and [is_pred] whether to follow hpred edges. *)
val get_succs : t -> node -> bool -> bool -> node list
(** [get_edges footprint_part g] returns the list of edges in [g], in the footprint part if [fotprint_part] is true *)
(** [get_edges footprint_part g] returns the list of edges in [g],
in the footprint part if [fotprint_part] is true *)
val get_edges : bool -> t -> edge list
(** [contains_edge footprint_part g e] returns true if the graph [g] contains edge [e],
searching the footprint part if [footprint_part] is true. *)
val contains_edge : bool -> t -> edge -> bool
(** [iter_edges footprint_part f g] iterates function [f] on the edges in [g] in the same order as returned by [get_edges];
(** [iter_edges footprint_part f g] iterates function [f] on the edges in [g]
in the same order as returned by [get_edges];
if [footprint_part] is true the edges are taken from the footprint part. *)
val iter_edges : bool -> (edge -> unit) -> t -> unit
(** Graph annotated with the differences w.r.t. a previous graph *)
type diff
(** [compute_diff default_color oldgraph newgraph] returns the list of edges which are only in [newgraph] *)
(** [compute_diff default_color oldgraph newgraph] returns the list of edges
which are only in [newgraph] *)
val compute_diff : color -> t -> t -> diff
(** [diff_get_colormap footprint_part diff] returns the colormap of a computed diff,
selecting the footprint colormap if [footprint_part] is true. *)
val diff_get_colormap : bool -> diff -> Utils.colormap
val diff_get_colormap : bool -> diff -> colormap
(** Print a list of propositions, prepending each one with the given string,
If !Config.pring_using_diff is true, print the diff w.r.t. the given prop,
extracting its local stack vars if the boolean is true. *)
val pp_proplist : printenv -> string -> (Prop.normal Prop.t * bool) -> Format.formatter -> Prop.normal Prop.t list -> unit
val pp_proplist :
printenv -> string -> (Prop.normal Prop.t * bool) ->
Format.formatter -> Prop.normal Prop.t list -> unit
(** dump a prop list coming form the given initial prop *)
val d_proplist : 'a Prop.t -> 'b Prop.t list -> unit

@ -693,14 +693,13 @@ let check_lt_normalized prop e1 e2 =
| Some upper1, Some lower2 -> Sil.Int.leq upper1 lower2 in
(upper_lower_check ()) || (Inequalities.check_lt ineq e1 e2)
(* given an atom and a proposition returns a unique identifier. *)
(* We use this to distinguish among different queries *)
(** Given an atom and a proposition returns a unique identifier.
We use this to distinguish among different queries. *)
let get_smt_key a p =
let tmp_filename = Filename.temp_file "smt_query" ".cns" in
let outc_tmp = open_out tmp_filename in
let fmt_tmp = F.formatter_of_out_channel outc_tmp in
let pe = Utils.pe_text in
let () = F.fprintf fmt_tmp "%a%a" (Sil.pp_atom pe) a (Prop.pp_prop pe) p in
let () = F.fprintf fmt_tmp "%a%a" (Sil.pp_atom pe_text) a (Prop.pp_prop pe_text) p in
close_out outc_tmp;
Digest.to_hex (Digest.file tmp_filename)
@ -709,20 +708,23 @@ let check_atom prop a0 =
let a = Prop.atom_normalize_prop prop a0 in
let prop_no_fp = Prop.replace_sigma_footprint [] (Prop.replace_pi_footprint [] prop) in
if !Config.smt_output then begin
let pe = Utils.pe_text in
let key = get_smt_key a prop_no_fp in
let key_filename = DB.Results_dir.path_to_filename DB.Results_dir.Abs_source_dir [(key ^ ".cns")] in
let key_filename =
DB.Results_dir.path_to_filename DB.Results_dir.Abs_source_dir [(key ^ ".cns")] in
let outc = open_out (DB.filename_to_string key_filename) in
let fmt = F.formatter_of_out_channel outc in
L.d_str ("ID: "^key); L.d_ln ();
L.d_str "CHECK_ATOM_BOUND: "; Sil.d_atom a; L.d_ln ();
L.d_str "WHERE:"; L.d_ln(); Prop.d_prop prop_no_fp; L.d_ln (); L.d_ln ();
let () = F.fprintf fmt "ID: %s @\nCHECK_ATOM_BOUND: %a@\nWHERE:@\n%a" key (Sil.pp_atom pe) a (Prop.pp_prop pe) prop_no_fp in
F.fprintf fmt "ID: %s @\nCHECK_ATOM_BOUND: %a@\nWHERE:@\n%a"
key (Sil.pp_atom pe_text) a (Prop.pp_prop pe_text) prop_no_fp;
close_out outc;
end;
match a with
| Sil.Aeq (Sil.BinOp (Sil.Le, e1, e2), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> check_le_normalized prop e1 e2
| Sil.Aeq (Sil.BinOp (Sil.Lt, e1, e2), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> check_lt_normalized prop e1 e2
| Sil.Aeq (Sil.BinOp (Sil.Le, e1, e2), Sil.Const (Sil.Cint i))
when Sil.Int.isone i -> check_le_normalized prop e1 e2
| Sil.Aeq (Sil.BinOp (Sil.Lt, e1, e2), Sil.Const (Sil.Cint i))
when Sil.Int.isone i -> check_lt_normalized prop e1 e2
| Sil.Aeq (e1, e2) -> check_equal prop e1 e2
| Sil.Aneq (e1, e2) -> check_disequal prop e1 e2
@ -765,7 +767,8 @@ let check_inconsistency_two_hpreds prop =
| (Sil.Hdllseg (Sil.Lseg_NE, _, iF, _, _, iB, _) as hpred) :: sigma_rest ->
if Sil.exp_equal iF e || Sil.exp_equal iB e then true
else f e (hpred:: sigma_seen) sigma_rest
| Sil.Hlseg (Sil.Lseg_PE, _, e1, Sil.Const (Sil.Cint i), _) as hpred :: sigma_rest when Sil.Int.iszero i ->
| Sil.Hlseg (Sil.Lseg_PE, _, e1, Sil.Const (Sil.Cint i), _) as hpred :: sigma_rest
when Sil.Int.iszero i ->
if Sil.exp_equal e1 e then true
else f e (hpred:: sigma_seen) sigma_rest
| Sil.Hlseg (Sil.Lseg_PE, _, e1, e2, _) as hpred :: sigma_rest ->
@ -811,14 +814,19 @@ let check_inconsistency_base prop =
let inconsistent_ptsto _ =
check_allocatedness prop Sil.exp_zero in
let inconsistent_this_self_var () =
let procdesc = Cfg.Node.get_proc_desc (State.get_node ()) in
let procedure_attr = Cfg.Procdesc.get_attributes procdesc in
let is_java_this pvar = !Config.curr_language = Config.Java && Sil.pvar_is_this pvar in
let is_objc_instance_self pvar = !Config.curr_language = Config.C_CPP &&
Sil.pvar_get_name pvar = Mangled.from_string "self" &&
procedure_attr.ProcAttributes.is_objc_instance_method in
let is_cpp_this pvar = !Config.curr_language = Config.C_CPP && Sil.pvar_is_this pvar &&
procedure_attr.ProcAttributes.is_cpp_instance_method in
let procdesc =
Cfg.Node.get_proc_desc (State.get_node ()) in
let procedure_attr =
Cfg.Procdesc.get_attributes procdesc in
let is_java_this pvar =
!Config.curr_language = Config.Java && Sil.pvar_is_this pvar in
let is_objc_instance_self pvar =
!Config.curr_language = Config.C_CPP &&
Sil.pvar_get_name pvar = Mangled.from_string "self" &&
procedure_attr.ProcAttributes.is_objc_instance_method in
let is_cpp_this pvar =
!Config.curr_language = Config.C_CPP && Sil.pvar_is_this pvar &&
procedure_attr.ProcAttributes.is_cpp_instance_method in
let do_hpred = function
| Sil.Hpointsto (Sil.Lvar pv, Sil.Eexp (e, _), _) ->
Sil.exp_equal e Sil.exp_zero &&

@ -72,7 +72,7 @@ let pp_item_annotation fmt item_annotation =
let item_annotation_to_string ann =
let pp fmt () = pp_item_annotation fmt ann in
Utils.pp_to_string pp ()
pp_to_string pp ()
(** Pretty print a method annotation. *)
let pp_method_annotation s fmt (ia, ial) =
@ -551,7 +551,7 @@ end = struct
else F.fprintf f "%Ld" n
let to_string i =
Utils.pp_to_string pp i
pp_to_string pp i
end
(** Flags for a procedure call *)
@ -2072,8 +2072,8 @@ and pp_exp_full pe f e =
and exp_to_string e = pp_to_string (pp_exp pe_text) e
let typ_to_string typ =
let pp fmt () = pp_typ_full Utils.pe_text fmt typ in
Utils.pp_to_string pp ()
let pp fmt () = pp_typ_full pe_text fmt typ in
pp_to_string pp ()
(** dump a type with all the details. *)
let d_typ_full (t: typ) = L.add_print_action (L.PTtyp_full, Obj.repr t)

@ -261,7 +261,10 @@ module CallStats = struct (** module for tracing stats of function calls *)
let s2 = if in_footprint then "FP" else "RE" in
s1 ^ ":" ^ s2
let pp_trace fmt tr = Utils.pp_seq (fun fmt x -> F.fprintf fmt "%s" (tr_elem_str x)) fmt (IList.rev tr)
let pp_trace fmt tr =
pp_seq
(fun fmt x -> F.fprintf fmt "%s" (tr_elem_str x))
fmt (IList.rev tr)
let iter f t =
let elems = ref [] in
@ -723,19 +726,6 @@ let get_flag proc_name key =
Some (Hashtbl.find proc_flags key)
with Not_found -> None
(** Get the iterations associated to the procedure if any, or the default timeout from the
command line *)
let get_iterations proc_name =
match get_summary proc_name with
| None ->
raise (Failure ("Specs.get_iterations: " ^ (Procname.to_string proc_name) ^ "Not_found"))
| Some summary ->
let proc_flags = summary.attributes.ProcAttributes.proc_flags in
try
let time_str = Hashtbl.find proc_flags proc_flag_iterations in
Pervasives.int_of_string time_str
with exn when exn_not_failure exn -> !iterations_cmdline
(** Return the specs and parameters for the proc in the spec table *)
let get_specs_formals proc_name =
match get_summary proc_name with

@ -177,9 +177,6 @@ val get_formals : summary -> (Mangled.t * Sil.typ) list
(** Get the flag with the given key for the procedure, if any *)
val get_flag : Procname.t -> string -> string option
(** Get the iterations associated to the procedure if any, or the default timeout from the command line *)
val get_iterations : Procname.t -> int
(** Return the current phase for the proc *)
val get_phase : Procname.t -> phase

@ -16,15 +16,17 @@ type splitting
(** Remove constant string or class from a prop *)
val remove_constant_string_class : 'a Prop.t -> Prop.normal Prop.t
(** Check if the attribute change is a mismatch between a kind of allocation and a different kind of deallocation *)
(** Check if the attribute change is a mismatch between a kind of allocation
and a different kind of deallocation *)
val check_attr_dealloc_mismatch : Sil.attribute -> Sil.attribute -> unit
(** Check whether a sexp contains a dereference without null check, and return the line number and path position *)
(** Check whether a sexp contains a dereference without null check,
and return the line number and path position *)
val find_dereference_without_null_check_in_sexp : Sil.strexp -> (int * Sil.path_pos) option
(** raise a cast exception *)
val raise_cast_exception :
Utils.ml_loc -> Procname.t option -> Sil.exp -> Sil.exp -> Sil.exp -> 'a
ml_loc -> Procname.t option -> Sil.exp -> Sil.exp -> Sil.exp -> 'a
(** check if a prop is an exception *)
val prop_is_exn : Procname.t -> 'a Prop.t -> bool

@ -0,0 +1,126 @@
(*
* Copyright (c) 2016 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
module L = Logging
module F = Format
(** Handle timeout events *)
(** status of a timeout instance *)
type status =
{
(** Seconds remaining in the current timeout *)
seconds_remaining : float;
(** Internal State of SymOp *)
symop_state : SymOp.t
}
(** stack of suspended timeout instances *)
type timeouts_stack =
status list ref
module GlobalState = struct
let stack : timeouts_stack =
ref []
let pop () =
match !stack with
| top_status :: l ->
stack := l;
Some top_status
| [] ->
None
let push status =
stack := status :: !stack
end
let set_alarm nsecs =
match Config.os_type with
| Config.Unix | Config.Cygwin ->
ignore (Unix.setitimer Unix.ITIMER_REAL
{ Unix.it_interval = 3.0; (* try again after 3 seconds if the signal is lost *)
Unix.it_value = nsecs })
| Config.Win32 ->
SymOp.set_wallclock_alarm nsecs
let unset_alarm () =
match Config.os_type with
| Config.Unix | Config.Cygwin ->
set_alarm 0.0
| Config.Win32 ->
SymOp.unset_wallclock_alarm ()
let get_seconds_remaining () =
match Config.os_type with
| Config.Unix | Config.Cygwin ->
(Unix.getitimer Unix.ITIMER_REAL).Unix.it_value
| Config.Win32 ->
SymOp.get_remaining_wallclock_time ()
let get_current_status () =
let seconds_remaining = get_seconds_remaining () in
let symop_state = SymOp.save_state () in
{
seconds_remaining;
symop_state;
}
let set_status status =
SymOp.restore_state status.symop_state;
set_alarm status.seconds_remaining
let timeout_action _ =
unset_alarm ();
raise (Analysis_failure_exe (FKtimeout))
let () = begin
match Config.os_type with
| Config.Unix | Config.Cygwin ->
Sys.set_signal Sys.sigvtalrm (Sys.Signal_handle timeout_action);
Sys.set_signal Sys.sigalrm (Sys.Signal_handle timeout_action)
| Config.Win32 ->
SymOp.set_wallclock_timeout_handler timeout_action;
(* use the Gc alarm for periodic timeout checks *)
ignore (Gc.create_alarm SymOp.check_wallclock_alarm)
end
let unwind () =
unset_alarm ();
SymOp.unset_alarm ();
GlobalState.pop ()
let suspend_existing_timeout () =
let current_status = get_current_status () in
unset_alarm ();
GlobalState.push current_status
let resume_previous_timeout () =
let status_opt = unwind () in
Option.may set_status status_opt
let exe_timeout f x =
let suspend_existing_timeout_and_start_new_one () =
suspend_existing_timeout ();
set_alarm (get_timeout_seconds ());
SymOp.set_alarm () in
try
suspend_existing_timeout_and_start_new_one ();
f x;
resume_previous_timeout ();
None
with
| Analysis_failure_exe kind ->
resume_previous_timeout ();
Errdesc.warning_err (State.get_loc ()) "TIMEOUT: %a@." pp_failure_kind kind;
Some kind
| exe ->
resume_previous_timeout ();
raise exe

@ -0,0 +1,19 @@
(*
* Copyright (c) 2016 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
(** Handle timeout events *)
(** Execute the function up to a given timeout. *)
val exe_timeout : ('a -> unit) -> 'a -> failure_kind option
(** Resume a previously suspended timeout. *)
val resume_previous_timeout : unit -> unit
(** Suspend the current timeout. It must be resumed later. *)
val suspend_existing_timeout : unit -> unit

@ -277,9 +277,9 @@ let pp_failure_kind fmt = function
let symops_timeout, seconds_timeout =
(* default timeout and long timeout are the same for now, but this will change shortly *)
let default_symops_timeout = 333 in
let default_seconds_timeout = 10 in
let default_seconds_timeout = 10.0 in
let long_symops_timeout = 1000 in
let long_seconds_timeout = 30 in
let long_seconds_timeout = 30.0 in
if Config.analyze_models then
(* use longer timeouts when analyzing models *)
long_symops_timeout, long_seconds_timeout
@ -292,9 +292,6 @@ let symops_per_iteration = ref symops_timeout
(** number of seconds to multiply by the number of iterations, after which there is a timeout *)
let seconds_per_iteration = ref seconds_timeout
(** timeout value from the -iterations command line option *)
let iterations_cmdline = ref 1
(** Timeout in seconds for each function *)
let timeout_seconds = ref !seconds_per_iteration
@ -304,23 +301,53 @@ let timeout_symops = ref !symops_per_iteration
(** Set the timeout values in seconds and symops, computed as a multiple of the integer parameter *)
let set_iterations n =
timeout_symops := !symops_per_iteration * n;
timeout_seconds := !seconds_per_iteration * n
timeout_seconds := !seconds_per_iteration *. (float_of_int n)
let get_timeout_seconds () = !timeout_seconds
(** Count the number of symbolic operations *)
module SymOp = struct
(** Number of symop's *)
let symop_count = ref 0
(** Total number of symop's since the beginning *)
let symop_total = ref 0
(** Internal state of the module *)
type t =
{
(** Only throw timeout exception when alarm is active *)
mutable alarm_active : bool;
(** last wallclock set by an alarm, if any *)
mutable last_wallclock : float option;
(** Number of symop's *)
mutable symop_count : int;
(** Total number of symop's since the beginning *)
mutable symop_total : int;
(** Time in the beginning *)
mutable timer : float;
}
let initial () : t =
{
alarm_active = false;
last_wallclock = None;
symop_count = 0;
symop_total = 0;
timer = Unix.gettimeofday ();
}
(** Only throw timeout exception when alarm is active *)
let alarm_active = ref false
(** Global State *)
let gs : t ref = ref (initial ())
(** last wallclock set by an alarm, if any *)
let last_wallclock = ref None
(** Restore the old state. *)
let restore_state state =
gs := state
(** Return the old state, and revert the current state to the initial one. *)
let save_state () =
let old_state = !gs in
gs := initial ();
old_state
(** handler for the wallclock timeout *)
let wallclock_timeout_handler = ref None
@ -331,65 +358,66 @@ module SymOp = struct
(** Set the wallclock alarm checked at every pay() *)
let set_wallclock_alarm nsecs =
last_wallclock := Some (Unix.gettimeofday () +. float_of_int nsecs)
!gs.last_wallclock <- Some (Unix.gettimeofday () +. nsecs)
(** Unset the wallclock alarm checked at every pay() *)
let unset_wallclock_alarm () =
last_wallclock := None
!gs.last_wallclock <- None
(** if the wallclock alarm has expired, raise a timeout exception *)
let check_wallclock_alarm () =
match !last_wallclock, !wallclock_timeout_handler with
match !gs.last_wallclock, !wallclock_timeout_handler with
| Some alarm_time, Some handler when Unix.gettimeofday () >= alarm_time ->
unset_wallclock_alarm ();
handler ()
| _ -> ()
(** Return the time remaining before the wallclock alarm expires *)
let get_remaining_wallclock_time () =
match !gs.last_wallclock with
| Some alarm_time ->
max 0.0 (alarm_time -. Unix.gettimeofday ())
| None ->
0.0
(** Return the total number of symop's since the beginning *)
let get_total () = !symop_total
let get_total () =
!gs.symop_total
(** Reset the total number of symop's *)
let reset_total () = symop_total := 0
(** timer at load time *)
let initial_time = Unix.gettimeofday ()
(** Time in the beginning *)
let timer = ref initial_time
let reset_total () =
!gs.symop_total <- 0
(** Count one symop *)
let pay () =
incr symop_count; incr symop_total;
if !symop_count > !timeout_symops && !alarm_active
then raise (Analysis_failure_exe (FKsymops_timeout !symop_count));
!gs.symop_count <- !gs.symop_count + 1;
!gs.symop_total <- !gs.symop_total + 1;
if !gs.symop_count > !timeout_symops &&
!gs.alarm_active
then raise (Analysis_failure_exe (FKsymops_timeout !gs.symop_count));
check_wallclock_alarm ()
(** Reset the counters *)
let reset () =
symop_count := 0;
timer := Unix.gettimeofday ()
!gs.symop_count <- 0;
!gs.timer <- Unix.gettimeofday ()
(** Reset the counter and activate the alarm *)
let set_alarm () =
reset ();
alarm_active := true
!gs.alarm_active <- true
(** De-activate the alarm *)
let unset_alarm () =
alarm_active := false
!gs.alarm_active <- false
let report_stats f symops elapsed =
F.fprintf f "SymOp stats -- symops:%d time:%f symops/sec:%f" symops elapsed ((float_of_int symops) /. elapsed)
(** Report the stats since the last reset *)
let report f () =
let elapsed = Unix.gettimeofday () -. !timer in
report_stats f !symop_count elapsed
(** Report the stats since the loading of this module *)
let report_total f () =
let elapsed = Unix.gettimeofday () -. initial_time in
report_stats f !symop_total elapsed
let elapsed = Unix.gettimeofday () -. !gs.timer in
report_stats f !gs.symop_count elapsed
end
(** Check if the lhs is a substring of the rhs. *)
@ -879,7 +907,6 @@ type proc_flags = (string, string) Hashtbl.t
let proc_flags_empty () : proc_flags = Hashtbl.create 1
let proc_flag_iterations = "iterations"
let proc_flag_skip = "skip"
let proc_flag_ignore_return = "ignore_return"

@ -160,13 +160,10 @@ val initial_analysis_time : float
val symops_per_iteration : int ref
(** number of seconds to multiply by the number of iterations, after which there is a timeout *)
val seconds_per_iteration : int ref
(** timeout value from the -iterations command line option *)
val iterations_cmdline : int ref
val seconds_per_iteration : float ref
(** Timeout in seconds for each function *)
val get_timeout_seconds : unit -> int
val get_timeout_seconds : unit -> float
(** Set the timeout values in seconds and symops, computed as a multiple of the integer parameter *)
val set_iterations : int -> unit
@ -187,38 +184,47 @@ val pp_failure_kind : Format.formatter -> failure_kind -> unit
(** Count the number of symbolic operations *)
module SymOp : sig
(** Internal state of the module *)
type t
(** if the wallclock alarm has expired, raise a timeout exception *)
val check_wallclock_alarm : unit -> unit
(** Return the time remaining before the wallclock alarm expires *)
val get_remaining_wallclock_time : unit -> float
(** Return the total number of symop's since the beginning *)
val get_total : unit -> int
(** Count one symop *)
val pay : unit -> unit
(** Report the stats since the last reset *)
val report : Format.formatter -> unit -> unit
(** Reset the total number of symop's *)
val reset_total : unit -> unit
(** Restore the old state. *)
val restore_state : t -> unit
(** Return the old state, and revert the current state to the initial one. *)
val save_state : unit -> t
(** Reset the counter and activate the alarm *)
val set_alarm : unit -> unit
(** De-activate the alarm *)
val unset_alarm : unit -> unit
(** Set the wallclock alarm checked at every pay() *)
val set_wallclock_alarm : float -> unit
(** set the handler for the wallclock timeout *)
val set_wallclock_timeout_handler : (unit -> unit) -> unit
(** Set the wallclock alarm checked at every pay() *)
val set_wallclock_alarm : int -> unit
(** De-activate the alarm *)
val unset_alarm : unit -> unit
(** Unset the wallclock alarm checked at every pay() *)
val unset_wallclock_alarm : unit -> unit
(** if the wallclock alarm has expired, raise a timeout exception *)
val check_wallclock_alarm : unit -> unit
(** Return the total number of symop's since the beginning *)
val get_total : unit -> int
(** Reset the total number of symop's *)
val reset_total : unit -> unit
(** Report the stats since the last reset *)
val report : Format.formatter -> unit -> unit
(** Report the stats since the loading of this module *)
val report_total : Format.formatter -> unit -> unit
end
module Arg : sig
@ -328,7 +334,6 @@ end
type proc_flags = (string, string) Hashtbl.t
(** keys for proc_flags *)
val proc_flag_iterations : string (** key to specify procedure-specific iterations *)
val proc_flag_skip : string (** key to specify that a function should be treated as a skip function *)
val proc_flag_ignore_return : string (** key to specify that it is OK to ignore the return value *)

@ -14,7 +14,7 @@ let patch = @INFER_PATCH@
let commit = "@INFER_GIT_COMMIT@"
let branch = "@INFER_GIT_BRANCH@"
let is_release = Utils.string_equal "@INFER_IS_RELEASE@" "yes"
let is_release = string_equal "@INFER_IS_RELEASE@" "yes"
let tag = Printf.sprintf "v%d.%d.%d" major minor patch
let versionString =

@ -95,7 +95,7 @@ let curr_class_compare curr_class1 curr_class2 =
| ContextCls (_, _, _), _ -> -1
| _, ContextCls (_, _, _) -> 1
| ContextCategory (name1, cls1), ContextCategory (name2, cls2) ->
Utils.pair_compare String.compare String.compare (name1, cls1) (name2, cls2)
pair_compare String.compare String.compare (name1, cls1) (name2, cls2)
| ContextCategory (_, _), _ -> -1
| _, ContextCategory (_, _) -> 1
| ContextProtocol name1, ContextProtocol name2 ->

@ -94,10 +94,10 @@ struct
L.err "AST Element> %s IN FILE> %s @.@." pointer !CFrontend_config.json
let print_nodes nodes =
IList.iter (fun node -> print_endline (Cfg.Node.get_description Utils.pe_text node)) nodes
IList.iter (fun node -> print_endline (Cfg.Node.get_description pe_text node)) nodes
let instrs_to_string instrs =
let pp fmt () = Format.fprintf fmt "%a" (Sil.pp_instr_list Utils.pe_text) instrs in
let pp fmt () = Format.fprintf fmt "%a" (Sil.pp_instr_list pe_text) instrs in
pp_to_string pp ()
end

@ -88,7 +88,7 @@ let should_translate (loc_start, loc_end) =
DB.source_file_equal file !DB.current_source
in
let file_in_project file = match !Config.project_root with
| Some root -> Utils.string_is_prefix root file
| Some root -> string_is_prefix root file
| None -> false
in
let file_in_project = map_path_of file_in_project loc_end

@ -17,7 +17,7 @@ open CFrontend_utils
let arg_desc =
let desc =
(Utils.arg_desc_filter ["-results_dir"] Utils.base_arg_desc) @
(Utils.arg_desc_filter ["-results_dir"] base_arg_desc) @
[
"-c",
Arg.String (fun cfile -> CFrontend_config.source_file := Some cfile),
@ -53,7 +53,7 @@ let arg_desc =
Arg.Unit (fun _ -> CFrontend_config.testing_mode := true),
None,
"Mode for testing, where no libraries are translated, \
including enums defined in the libraries"
including enums defined in the libraries"
;
"-debug",
Arg.Unit (fun _ -> CFrontend_config.debug_mode := true),
@ -82,17 +82,17 @@ let arg_desc =
"Mode for computing the models"
;
] in
Utils.Arg.create_options_desc false "Parsing Options" desc
Arg.create_options_desc false "Parsing Options" desc
let usage =
"\nUsage: InferClang -c C Files -ast AST Files -results_dir <output-dir> [options] \n"
let print_usage_exit () =
Utils.Arg.usage arg_desc usage;
Arg.usage arg_desc usage;
exit(1)
let () =
Utils.Arg.parse arg_desc (fun _ -> ()) usage
Arg.parse arg_desc (fun _ -> ()) usage
let buffer_len = 16384

@ -1551,8 +1551,9 @@ struct
IList.map (fun (e, t) -> IList.flatten (collect_left_hand_exprs e t tns)) (zip lh_exprs lh_types)
| _ -> [ [(e, typ)] ] in
let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in
let var_type = CTypes_decl.type_ptr_to_sil_type context.CContext.tenv expr_info.Clang_ast_t.ei_type_ptr in
let lh = IList.flatten (collect_left_hand_exprs var_exp var_type Utils.StringSet.empty) in
let var_type =
CTypes_decl.type_ptr_to_sil_type context.CContext.tenv expr_info.Clang_ast_t.ei_type_ptr in
let lh = IList.flatten (collect_left_hand_exprs var_exp var_type StringSet.empty) in
let rh = IList.flatten (IList.map (collect_right_hand_exprs trans_state_pri) stmts ) in
if IList.length rh != IList.length lh then (
(* If the right hand expressions are not as many as the left hand expressions something's wrong *)

@ -250,7 +250,7 @@ let typecheck_instr ext calls_this checks (node: Cfg.Node.t) idenv get_proc_desc
let handle_field_access_via_temporary typestate exp =
let name_is_temporary name =
let prefix = "$T" in
Utils.string_is_prefix prefix name in
string_is_prefix prefix name in
let pvar_get_origin pvar =
match TypeState.lookup_pvar pvar typestate with
| Some (_, ta, _) ->

@ -16,7 +16,8 @@ val models_jar : string ref
(** Type environment of the models *)
val models_tenv : Sil.tenv ref
(** Adds the set of procnames for the models of Java libraries so that methods with similar names are skipped during the capture *)
(** Adds the set of procnames for the models of Java libraries so that methods
with similar names are skipped during the capture *)
val add_models : string -> unit
(** Check if there is a model for the given procname *)
@ -24,7 +25,8 @@ val is_model : Procname.t -> bool
val set_verbose_out: string -> unit
(** create a source file from an absolute path. Source files are relative if the project root is specified and absolute otherwise *)
(** create a source file from an absolute path.
Source files are relative if the project root is specified and absolute otherwise *)
val java_source_file_from_path : string -> DB.source_file
val split_classpath : string -> string list
@ -36,7 +38,7 @@ type file_entry =
(** load the list of source files and the list of classes from the javac verbose file *)
val load_sources_and_classes : unit ->
string * file_entry Utils.StringMap.t * JBasics.ClassSet.t
string * file_entry StringMap.t * JBasics.ClassSet.t
type classmap = JCode.jcode Javalib.interface_or_class JBasics.ClassMap.t

@ -9,7 +9,7 @@
(** Representation of LLVM constructs *)
module MetadataMap = Utils.IntMap;;
module MetadataMap = IntMap;;
type variable_id =
| Name of string

@ -28,7 +28,7 @@ let arg_desc =
let usage = "Usage: InferLLVM -c <cfile> [options]\n"
let print_usage_exit () =
Utils.Arg.usage arg_desc usage;
Arg.usage arg_desc usage;
exit(1)
let init_global_state source_filename =

@ -162,7 +162,7 @@ let copyright_has_changed mono fb_year com_style prefix cstart cend lines_arr =
!r in
let new_copyright =
let pp fmt () = pp_copyright mono fb_year com_style fmt prefix in
Utils.pp_to_string pp () in
pp_to_string pp () in
old_copyright <> new_copyright
let update_file fname mono fb_year com_style prefix cstart cend lines_arr =

Loading…
Cancel
Save