From 0b42f9cf06973c41eee960852873e0fae4c9592d Mon Sep 17 00:00:00 2001 From: Sam Blackshear Date: Tue, 25 Aug 2015 13:41:56 -0600 Subject: [PATCH] [Infer][incremental] Only analyze (transitive) callers of changed procedures Summary: Extending procedure-level incrementality to callers. Before, all procedures in a file depending on the changed procedure would be analyzed. --- infer/src/backend/inferanalyze.ml | 50 +++++++++++-------- .../java/incremental/ChildChangedTest.java | 11 +++- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/infer/src/backend/inferanalyze.ml b/infer/src/backend/inferanalyze.ml index b94a7ff05..1d0b240b5 100644 --- a/infer/src/backend/inferanalyze.ml +++ b/infer/src/backend/inferanalyze.ml @@ -388,7 +388,7 @@ let create_minimal_clusters file_cg exe_env to_analyze_map : cluster list = let proc_is_active pname = proc_is_selected pname && DB.source_file_equal (Exe_env.get_source exe_env pname) source_file in - let active_procs = list_filter proc_is_active changed_procs in + let active_procs = list_filter proc_is_active (Procname.Set.elements changed_procs) in let naprocs = list_length active_procs in let source_map = let all_procs, _ = Cg.get_nodes_and_edges cg in @@ -583,6 +583,9 @@ module ClusterMakefile = struct exit 0 end +let proc_list_to_set proc_list = + list_fold_left (fun s p -> Procname.Set.add p s) Procname.Set.empty proc_list + (** compute the clusters *) let compute_clusters exe_env files_changed : cluster list = if !trace_clusters then @@ -610,33 +613,36 @@ let compute_clusters exe_env files_changed : cluster list = let files = Cg.get_defined_nodes file_cg in let num_files = list_length files in L.err "@.Found %d defined procedures in %d files.@." (list_length defined_procs) num_files; - (* get procedures defined in a file *) - let get_defined_procs file_pname = match file_pname_to_cg file_pname with - | None -> [] - | Some cg -> Cg.get_defined_nodes cg in let to_analyze_map = match !incremental_mode with | ANALYZE_ALL -> + (* get procedures defined in a file *) + let get_defined_procs file_pname = match file_pname_to_cg file_pname with + | None -> Procname.Set.empty + | Some cg -> proc_list_to_set (Cg.get_defined_nodes cg) in list_fold_left (fun m file_pname -> Procname.Map.add file_pname (get_defined_procs file_pname) m) Procname.Map.empty files | ANALYZE_CHANGED_ONLY -> files_changed | ANALYZE_CHANGED_AND_DEPENDENCIES -> - (* get the set of files that depend on [file_pname] *) - let get_dependent_files file_pname = - try Cg.get_ancestors file_cg file_pname with - | Not_found -> - let p = Procname.to_string file_pname in - L.err "Warning: ignoring modified file %s; functions may be defined elsewhere@." p; - Procname.Set.empty in - (* add the dependencies of [file_pname] to [files_changed] *) - let add_dependent_files file_pname _ files_changed = - Procname.Set.fold - (fun dep files_changed -> Procname.Map.add dep (get_defined_procs dep) files_changed) - (get_dependent_files file_pname) - files_changed in - (* add files that depend on a changed file to the map along with their defined procedures *) - Procname.Map.fold add_dependent_files files_changed files_changed in + (* get all callers of changed procedures *) + let callers_of_changed = + let collect_callers_for_procs _ procs callers = + Procname.Set.fold + (fun proc callers -> Procname.Set.union callers (Cg.get_ancestors global_cg proc)) + procs + callers in + Procname.Map.fold collect_callers_for_procs files_changed Procname.Set.empty in + (* add a caller to the file -> (procedures to analyze) map *) + let add_caller_to_map caller map = + let caller_file_pname = source_file_to_pname (Exe_env.get_source exe_env caller) in + let procs_to_analyze = + try Procname.Map.find caller_file_pname map + with Not_found -> Procname.Set.empty in + let procs_to_analyze' = Procname.Set.add caller procs_to_analyze in + Procname.Map.add caller_file_pname procs_to_analyze' map in + (* add the (transitive) callers of all changed procedures to the [files_changed] map *) + Procname.Set.fold add_caller_to_map callers_of_changed files_changed in L.err "Analyzing %d files.@.@." (Procname.Map.cardinal to_analyze_map); let clusters = create_minimal_clusters file_cg exe_env to_analyze_map in L.err "Minimal clusters:@."; @@ -700,9 +706,9 @@ let compute_files_changed_map _exe_env (source_dirs : DB.source_dir list) exclud let changed_procs = if !incremental_mode = ANALYZE_ALL then Cg.get_defined_nodes cg else cg_get_changed_procs exe_env source_dir cg in - if changed_procs != [] then + if changed_procs <> [] then let file_pname = source_file_to_pname (Cg.get_source cg) in - Procname.Map.add file_pname changed_procs files_changed_map + Procname.Map.add file_pname (proc_list_to_set changed_procs) files_changed_map else files_changed_map in list_fold_left cg_get_files_changed files_changed_map cg_list in let exe_env = Exe_env.freeze _exe_env in diff --git a/infer/tests/endtoend/java/incremental/ChildChangedTest.java b/infer/tests/endtoend/java/incremental/ChildChangedTest.java index a2b09d37d..c5d9d4973 100644 --- a/infer/tests/endtoend/java/incremental/ChildChangedTest.java +++ b/infer/tests/endtoend/java/incremental/ChildChangedTest.java @@ -37,7 +37,7 @@ public class ChildChangedTest { } @Test - public void unchangedFileNotReanalyzedInIncrementalMode() + public void changedFilesReanalyzedInIncrementalMode() throws IOException, InterruptedException, InferException { assertThat( "After changing the child file, parent and grandparent should be re-analyzed", @@ -45,4 +45,13 @@ public class ChildChangedTest { numberOfFilesAnalyzed(3)); } + @Test + public void onlyChangedProcsReanalyzedInIncrementalMode() + throws IOException, InterruptedException, InferException { + assertThat( + "After changing a procedure, only the proc and its callers should be re-analyzed", + inferStats, + numberOfProceduresAnalyzed(3)); + } + }