source/service/staticAnalysis.ml (701 lines of code) (raw):
(*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open Core
open Ast
open Statement
open Pyre
module Target = Interprocedural.Target
module TypeEnvironment = Analysis.TypeEnvironment
module AstEnvironment = Analysis.AstEnvironment
module GlobalResolution = Analysis.GlobalResolution
module DependencyGraph = Interprocedural.DependencyGraph
module DependencyGraphSharedMemory = Interprocedural.DependencyGraphSharedMemory
module ClassHierarchyGraph = Interprocedural.ClassHierarchyGraph
(* The boolean indicated whether the callable is internal or not. *)
type callable_with_dependency_information = Target.callable_t * bool
type initial_callables = {
callables_with_dependency_information: callable_with_dependency_information list;
stubs: Target.callable_t list;
filtered_callables: Target.Set.t;
}
module InitialCallablesSharedMemory = Memory.Serializer (struct
type t = initial_callables
module Serialized = struct
type t = initial_callables
let prefix = Prefix.make ()
let description = "Initial callables to analyze"
let unmarshall value = Marshal.from_string value 0
end
let serialize = Fn.id
let deserialize = Fn.id
end)
module ClassHierarchyGraphSharedMemory = Memory.Serializer (struct
type t = ClassHierarchyGraph.t
module Serialized = struct
type t = ClassHierarchyGraph.t
let prefix = Prefix.make ()
let description = "Class hierarchy graph"
let unmarshall value = Marshal.from_string value 0
end
let serialize = Fn.id
let deserialize = Fn.id
end)
module Cache = struct
type cached = { module_tracker: Analysis.ModuleTracker.t }
type error =
| InvalidByCodeChange
| LoadError
| NotFound
| Disabled
type t = {
cache: (cached, error) Result.t;
save_cache: bool;
scheduler: Scheduler.t;
configuration: Configuration.Analysis.t;
}
let get_save_directory ~configuration =
PyrePath.create_relative
~root:(Configuration.Analysis.log_directory configuration)
~relative:".pysa_cache"
let get_shared_memory_save_path ~configuration =
PyrePath.append (get_save_directory ~configuration) ~element:"sharedmem"
let get_overrides_save_path ~configuration =
PyrePath.append (get_save_directory ~configuration) ~element:"overrides"
let get_callgraph_save_path ~configuration =
PyrePath.append (get_save_directory ~configuration) ~element:"callgraph"
let exception_to_error ~error ~message ~f =
try f () with
| exception_ ->
Log.error "Error %s:\n%s" message (Exn.to_string exception_);
Error error
let ignore_result (_ : ('a, 'b) result) = ()
let initialize_shared_memory ~configuration =
let path = get_shared_memory_save_path ~configuration in
if not (PyrePath.file_exists path) then (
Log.warning "Could not find a cached state.";
Error NotFound)
else
exception_to_error ~error:LoadError ~message:"loading cached state" ~f:(fun () ->
let _ = Memory.get_heap_handle configuration in
Memory.load_shared_memory ~path:(PyrePath.absolute path) ~configuration;
Log.warning
"Cached state successfully loaded from `%s`."
(PyrePath.absolute (get_save_directory ~configuration));
Ok ())
let load_module_tracker ~scheduler ~configuration =
let open Result in
Log.info "Determining if source files have changed since cache was created.";
exception_to_error ~error:LoadError ~message:"loading module tracker from cache" ~f:(fun () ->
Ok (Analysis.ModuleTracker.SharedMemory.load ()))
>>= fun old_module_tracker ->
let new_module_tracker = Analysis.ModuleTracker.create configuration in
let changed_paths =
let is_pysa_model path = String.is_suffix ~suffix:".pysa" (PyrePath.get_suffix_path path) in
let is_taint_config path = String.is_suffix ~suffix:"taint.config" (PyrePath.absolute path) in
ChangedPaths.compute_locally_changed_paths
~scheduler
~configuration
~old_module_tracker
~new_module_tracker
|> List.filter ~f:(fun path -> not (is_pysa_model path || is_taint_config path))
in
match changed_paths with
| [] -> Ok new_module_tracker
| _ ->
Log.warning "Changes to source files detected, ignoring existing cache.";
Error InvalidByCodeChange
let load ~scheduler ~configuration ~enabled =
if not enabled then
{ cache = Error Disabled; save_cache = false; scheduler; configuration }
else
let open Result in
let module_tracker =
initialize_shared_memory ~configuration
>>= fun () -> load_module_tracker ~scheduler ~configuration
in
let cache =
match module_tracker with
| Ok module_tracker -> Ok { module_tracker }
| Error error ->
Memory.reset_shared_memory ();
Error error
in
{ cache; save_cache = true; scheduler; configuration }
let load_type_environment ~module_tracker =
exception_to_error ~error:LoadError ~message:"loading type environment from cache" ~f:(fun () ->
let ast_environment = AstEnvironment.load module_tracker in
let environment =
Analysis.AnnotatedGlobalEnvironment.create ast_environment |> TypeEnvironment.create
in
Analysis.SharedMemoryKeys.DependencyKey.Registry.load ();
Log.info "Loaded cached type environment.";
Ok environment)
let save_type_environment ~scheduler ~configuration ~environment =
exception_to_error ~error:() ~message:"saving type environment to cache" ~f:(fun () ->
Memory.SharedMemory.collect `aggressive;
let module_tracker = TypeEnvironment.module_tracker environment in
let ast_environment = TypeEnvironment.ast_environment environment in
ChangedPaths.save_current_paths ~scheduler ~configuration ~module_tracker;
Analysis.ModuleTracker.SharedMemory.store module_tracker;
AstEnvironment.store ast_environment;
Analysis.SharedMemoryKeys.DependencyKey.Registry.store ();
Log.info "Saved type environment to cache shared memory.";
Ok ())
let type_environment { cache; save_cache; scheduler; configuration } f =
let type_environment =
match cache with
| Ok { module_tracker } -> load_type_environment ~module_tracker |> Result.ok
| _ -> None
in
match type_environment with
| Some type_environment -> type_environment
| None ->
let environment = f () in
if save_cache then
save_type_environment ~scheduler ~configuration ~environment |> ignore_result;
environment
let load_initial_callables () =
exception_to_error
~error:LoadError
~message:"loading initial callables from cache"
~f:(fun () ->
let initial_callables = InitialCallablesSharedMemory.load () in
Log.info "Loaded cached initial callables.";
Ok initial_callables)
let ensure_save_directory_exists ~configuration =
let directory = PyrePath.absolute (get_save_directory ~configuration) in
try Core.Unix.mkdir directory with
(* [mkdir] on MacOSX returns [EISDIR] instead of [EEXIST] if the directory already exists. *)
| Core.Unix.Unix_error ((EEXIST | EISDIR), _, _) -> ()
| e -> raise e
let save_shared_memory ~configuration =
exception_to_error ~error:() ~message:"saving cached state to file" ~f:(fun () ->
let path = get_shared_memory_save_path ~configuration in
Log.info "Saving shared memory state to cache file...";
ensure_save_directory_exists ~configuration;
Memory.save_shared_memory ~path:(PyrePath.absolute path) ~configuration;
Log.info "Saved shared memory state to cache file: `%s`" (PyrePath.absolute path);
Ok ())
let save_initial_callables ~configuration ~initial_callables =
exception_to_error ~error:() ~message:"saving initial callables to cache" ~f:(fun () ->
Memory.SharedMemory.collect `aggressive;
InitialCallablesSharedMemory.store initial_callables;
Log.info "Saved initial callables to cache shared memory.";
(* Shared memory is saved to file after caching the callables to shared memory. The
remaining overrides and callgraph to be cached don't use shared memory and are saved as
serialized sexps to separate files. *)
save_shared_memory ~configuration)
let initial_callables { cache; save_cache; configuration; _ } f =
let initial_callables =
match cache with
| Ok _ -> load_initial_callables () |> Result.ok
| _ -> None
in
match initial_callables with
| Some initial_callables -> initial_callables
| None ->
let callables = f () in
if save_cache then
save_initial_callables ~configuration ~initial_callables:callables |> ignore_result;
callables
let load_overrides ~configuration =
exception_to_error ~error:LoadError ~message:"loading overrides from cache" ~f:(fun () ->
let path = get_overrides_save_path ~configuration in
let sexp = Sexplib.Sexp.load_sexp (PyrePath.absolute path) in
let overrides = Reference.Map.t_of_sexp (Core.List.t_of_sexp Reference.t_of_sexp) sexp in
Log.info "Loaded overrides from cache.";
Ok overrides)
let save_overrides ~configuration ~overrides =
exception_to_error ~error:() ~message:"saving overrides to cache" ~f:(fun () ->
let path = get_overrides_save_path ~configuration in
let data = Reference.Map.sexp_of_t (Core.List.sexp_of_t Reference.sexp_of_t) overrides in
ensure_save_directory_exists ~configuration;
Sexplib.Sexp.save (PyrePath.absolute path) data;
Log.info "Saved overrides to cache file: `%s`" (PyrePath.absolute path);
Ok ())
let overrides { cache; save_cache; configuration; _ } f =
let overrides =
match cache with
| Ok _ -> load_overrides ~configuration |> Result.ok
| _ -> None
in
match overrides with
| Some overrides -> overrides
| None ->
let overrides = f () in
if save_cache then save_overrides ~configuration ~overrides |> ignore_result;
overrides
let load_call_graph ~configuration =
exception_to_error ~error:LoadError ~message:"loading call graph from cache" ~f:(fun () ->
let path = get_callgraph_save_path ~configuration in
let sexp = Sexplib.Sexp.load_sexp (PyrePath.absolute path) in
let callgraph = Target.CallableMap.t_of_sexp (Core.List.t_of_sexp Target.t_of_sexp) sexp in
Log.info "Loaded call graph from cache.";
Ok callgraph)
let save_call_graph ~configuration ~call_graph =
exception_to_error ~error:() ~message:"saving call graph to cache" ~f:(fun () ->
let path = get_callgraph_save_path ~configuration in
let data = Target.CallableMap.sexp_of_t (Core.List.sexp_of_t Target.sexp_of_t) call_graph in
ensure_save_directory_exists ~configuration;
Sexplib.Sexp.save (PyrePath.absolute path) data;
Log.info "Saved call graph to cache file: `%s`" (PyrePath.absolute path);
Ok ())
let call_graph { cache; save_cache; configuration; _ } f =
let call_graph =
match cache with
| Ok _ -> load_call_graph ~configuration |> Result.ok
| _ -> None
in
match call_graph with
| Some call_graph -> call_graph
| None ->
let call_graph = f () in
if save_cache then save_call_graph ~configuration ~call_graph |> ignore_result;
call_graph
let load_class_hierarchy_graph () =
exception_to_error
~error:LoadError
~message:"loading class hierarchy graph from cache"
~f:(fun () ->
let class_hierarchy_graph = ClassHierarchyGraphSharedMemory.load () in
Log.info "Loaded class hierarchy graph.";
Ok class_hierarchy_graph)
let save_class_hierarchy_graph ~class_hierarchy_graph =
exception_to_error ~error:() ~message:"saving class hierarchy graph to cache" ~f:(fun () ->
Memory.SharedMemory.collect `aggressive;
ClassHierarchyGraphSharedMemory.store class_hierarchy_graph;
Log.info "Saved class hierarchy graph to cache shared memory.";
Ok ())
let class_hierarchy_graph { cache; save_cache; _ } f =
let class_hierarchy_graph =
match cache with
| Ok _ -> load_class_hierarchy_graph () |> Result.ok
| _ -> None
in
match class_hierarchy_graph with
| Some class_hierarchy_graph -> class_hierarchy_graph
| None ->
let class_hierarchy_graph = f () in
if save_cache then
save_class_hierarchy_graph ~class_hierarchy_graph |> ignore_result;
class_hierarchy_graph
end
(* Perform a full type check and build a type environment. *)
let type_check ~scheduler ~configuration ~cache =
Cache.type_environment cache (fun () ->
let configuration =
(* In order to get an accurate call graph and type information, we need to ensure that we
schedule a type check for external files. *)
{ configuration with Configuration.Analysis.analyze_external_sources = true }
in
Check.check
~scheduler
~configuration
~call_graph_builder:(module Analysis.Callgraph.NullBuilder)
|> fun { environment; _ } -> environment)
let parse_and_save_decorators_to_skip
~inline_decorators
{ Configuration.Analysis.taint_model_paths; _ }
=
Analysis.InlineDecorator.set_should_inline_decorators inline_decorators;
if inline_decorators then (
let timer = Timer.start () in
Log.info "Getting decorators to skip when inlining...";
let model_sources = Taint.ModelParser.get_model_sources ~paths:taint_model_paths in
let decorators_to_skip =
List.concat_map model_sources ~f:(fun (path, source) ->
Analysis.InlineDecorator.decorators_to_skip ~path source)
in
List.iter decorators_to_skip ~f:(fun decorator ->
Analysis.InlineDecorator.DecoratorsToSkip.add decorator decorator);
Statistics.performance
~name:"Getting decorators to skip when inlining"
~phase_name:"Getting decorators to skip when inlining"
~timer
())
let record_and_merge_call_graph ~environment ~call_graph ~source =
let record_and_merge_call_graph map call_graph =
Map.merge_skewed map call_graph ~combine:(fun ~key:_ left _ -> left)
in
Interprocedural.CallGraph.create_callgraph ~use_shared_memory:true ~environment ~source
|> record_and_merge_call_graph call_graph
let unfiltered_callables ~resolution ~source:{ Source.source_path = { SourcePath.qualifier; _ }; _ }
=
let defines =
GlobalResolution.unannotated_global_environment resolution
|> (fun environment ->
Analysis.UnannotatedGlobalEnvironment.ReadOnly.all_defines_in_module environment qualifier)
|> List.filter_map ~f:(GlobalResolution.function_definitions resolution)
|> List.concat
|> List.filter ~f:(fun { Node.value = define; _ } -> not (Define.is_overloaded_function define))
in
List.map ~f:(fun define -> Target.create define, define) defines
type found_callable = {
callable: Target.callable_t;
define: Define.t Node.t;
is_internal: bool;
}
let regular_and_filtered_callables ~configuration ~resolution ~source =
let callables = unfiltered_callables ~resolution ~source in
let included, filtered =
if GlobalResolution.source_is_unit_test resolution ~source then
[], List.map callables ~f:fst
else if Ast.SourcePath.is_stub source.source_path then
( List.filter callables ~f:(fun (_, { Node.value = define; _ }) ->
not (Define.is_toplevel define || Define.is_class_toplevel define)),
[] )
else
callables, []
in
let is_internal_source =
Ast.SourcePath.is_internal_path
~configuration
(Ast.SourcePath.full_path ~configuration source.source_path)
in
( List.map included ~f:(fun (callable, define) ->
{ callable; define; is_internal = is_internal_source }),
filtered )
let get_source ~environment qualifier =
let ast_environment = TypeEnvironment.ReadOnly.ast_environment environment in
AstEnvironment.ReadOnly.get_processed_source ast_environment qualifier
let fetch_callables_to_analyze ~scheduler ~environment ~configuration ~qualifiers =
let global_resolution = TypeEnvironment.ReadOnly.global_resolution environment in
let classify_source
(callables, stubs)
{ callable; define = { Node.value = define; _ }; is_internal }
=
if Define.is_stub define then
callables, callable :: stubs
else
(callable, is_internal) :: callables, stubs
in
let map result qualifiers =
let make_callables
({
callables_with_dependency_information = existing_callables;
stubs = existing_stubs;
filtered_callables = existing_filtered_callables;
} as result)
qualifier
=
get_source ~environment qualifier
>>| (fun source ->
let callables, new_filtered_callables =
regular_and_filtered_callables ~configuration ~resolution:global_resolution ~source
in
let callables, stubs =
List.fold callables ~f:classify_source ~init:(existing_callables, existing_stubs)
in
let filtered_callables =
List.fold
new_filtered_callables
~init:existing_filtered_callables
~f:(Fn.flip Target.Set.add)
in
{ callables_with_dependency_information = callables; stubs; filtered_callables })
|> Option.value ~default:result
in
List.fold qualifiers ~f:make_callables ~init:result
in
let reduce
{
callables_with_dependency_information = new_callables;
stubs = new_stubs;
filtered_callables = new_filtered_callables;
}
{ callables_with_dependency_information = callables; stubs; filtered_callables }
=
{
callables_with_dependency_information = List.rev_append new_callables callables;
stubs = List.rev_append new_stubs stubs;
filtered_callables = Target.Set.union new_filtered_callables filtered_callables;
}
in
Scheduler.map_reduce
scheduler
~policy:
(Scheduler.Policy.fixed_chunk_count ~minimum_chunk_size:50 ~preferred_chunks_per_worker:1 ())
~map
~reduce
~initial:
{
callables_with_dependency_information = [];
stubs = [];
filtered_callables = Target.Set.empty;
}
~inputs:qualifiers
()
(* Traverse the AST to find all callables (functions and methods), filtering out callables from test
files. *)
let fetch_initial_callables ~scheduler ~configuration ~cache ~environment ~qualifiers =
Cache.initial_callables cache (fun () ->
let timer = Timer.start () in
let initial_callables =
fetch_callables_to_analyze ~scheduler ~environment ~configuration ~qualifiers
in
Statistics.performance
~name:"Fetched initial callables to analyze"
~phase_name:"Fetching initial callables to analyze"
~timer
();
initial_callables)
let build_class_hierarchy_graph ~scheduler ~cache ~environment ~qualifiers =
Cache.class_hierarchy_graph cache (fun () ->
let timer = Timer.start () in
let build_class_hierarchy_graph _ qualifiers =
List.fold qualifiers ~init:ClassHierarchyGraph.empty ~f:(fun accumulator qualifier ->
match get_source ~environment qualifier with
| Some source ->
let graph = ClassHierarchyGraph.from_source ~environment ~source in
ClassHierarchyGraph.join accumulator graph
| None -> accumulator)
in
let class_hierarchy_graph =
Scheduler.map_reduce
scheduler
~policy:(Scheduler.Policy.legacy_fixed_chunk_count ())
~initial:ClassHierarchyGraph.empty
~map:build_class_hierarchy_graph
~reduce:ClassHierarchyGraph.join
~inputs:qualifiers
()
in
Statistics.performance
~name:"Computed class hierarchy graph"
~phase_name:"Computing class hierarchy graph"
~timer
();
class_hierarchy_graph)
let build_class_intervals class_hierarchy_graph =
let timer = Timer.start () in
Interprocedural.ClassInterval.compute_intervals class_hierarchy_graph
|> Interprocedural.ClassInterval.SharedMemory.store;
Statistics.performance
~name:"Computed class intervals"
~phase_name:"Computing class intervals"
~timer
()
(* Compute the override graph, which maps overide_targets (parent methods which are overridden) to
all concrete methods overriding them, and save it to shared memory. *)
let record_overrides_for_qualifiers ~scheduler ~cache ~environment ~skip_overrides ~qualifiers =
let overrides =
Cache.overrides cache (fun () ->
let combine ~key:_ left right = List.rev_append left right in
let build_overrides overrides qualifier =
try
match get_source ~environment qualifier with
| None -> overrides
| Some source ->
let new_overrides =
DependencyGraph.create_overrides ~environment ~source
|> Reference.Map.filter_keys ~f:(fun override ->
not (Reference.Set.mem skip_overrides override))
in
Map.merge_skewed overrides new_overrides ~combine
with
| Analysis.ClassHierarchy.Untracked untracked_type ->
Log.warning
"Error building overrides in path %a for untracked type %s"
Reference.pp
qualifier
untracked_type;
overrides
in
Scheduler.map_reduce
scheduler
~policy:(Scheduler.Policy.legacy_fixed_chunk_count ())
~initial:DependencyGraph.empty_overrides
~map:(fun _ qualifiers ->
List.fold qualifiers ~init:DependencyGraph.empty_overrides ~f:build_overrides)
~reduce:(Map.merge_skewed ~combine)
~inputs:qualifiers
())
in
let {
Taint.TaintConfiguration.analysis_model_constraints = { maximum_overrides_to_analyze; _ };
_;
}
=
Taint.TaintConfiguration.get ()
in
let ({ DependencyGraphSharedMemory.overrides; _ } as cap_override_result) =
DependencyGraphSharedMemory.cap_overrides ?maximum_overrides_to_analyze overrides
in
DependencyGraphSharedMemory.record_overrides overrides;
cap_override_result
(* Build the callgraph, a map from caller to callees. The overrides must be computed first because
we depend on a global shared memory graph to include overrides in the call graph. Without it,
we'll underanalyze and have an inconsistent fixpoint. *)
let build_call_graph ~scheduler ~static_analysis_configuration ~cache ~environment ~qualifiers =
let call_graph =
Cache.call_graph cache (fun () ->
let build_call_graph call_graph qualifier =
try
get_source ~environment qualifier
>>| (fun source -> record_and_merge_call_graph ~environment ~call_graph ~source)
|> Option.value ~default:call_graph
with
| Analysis.ClassHierarchy.Untracked untracked_type ->
Log.info
"Error building call graph in path %a for untracked type %s"
Reference.pp
qualifier
untracked_type;
call_graph
in
Scheduler.map_reduce
scheduler
~policy:(Scheduler.Policy.legacy_fixed_chunk_count ())
~initial:Target.CallableMap.empty
~map:(fun _ qualifiers ->
List.fold qualifiers ~init:Target.CallableMap.empty ~f:build_call_graph)
~reduce:(Map.merge_skewed ~combine:(fun ~key:_ left _ -> left))
~inputs:qualifiers
())
in
let () =
match static_analysis_configuration.Configuration.StaticAnalysis.dump_call_graph with
| Some path -> DependencyGraph.from_callgraph call_graph |> DependencyGraph.dump ~path
| None -> ()
in
call_graph
(* Merge overrides and callgraph into a combined dependency graph, and prune anything not linked to
the callables we are actually analyzing. Then reverse the graph, which maps dependers to
dependees (i.e. override targets to overrides + callers to callees) into a scheduling graph that
maps dependees to dependers. *)
let build_dependency_graph ~callables_with_dependency_information ~callgraph ~override_dependencies =
let override_targets = (Target.Map.keys override_dependencies :> Target.t list) in
let dependencies, callables_to_analyze =
let dependencies =
DependencyGraph.from_callgraph callgraph |> DependencyGraph.union override_dependencies
in
let { DependencyGraph.dependencies; pruned_callables } =
DependencyGraph.prune
dependencies
~callables_with_dependency_information:
(callables_with_dependency_information :> (Target.t * bool) list)
in
DependencyGraph.reverse dependencies, pruned_callables
in
(* Create an empty callable for each override target (on each iteration, the framework will update
these by joining models for all overrides *)
let () =
let add_predefined callable =
Interprocedural.FixpointState.add_predefined
Interprocedural.FixpointState.Epoch.initial
callable
Interprocedural.AnalysisResult.empty_model
in
List.iter override_targets ~f:add_predefined
in
dependencies, callables_to_analyze, override_targets
let purge_shared_memory ~environment ~qualifiers =
(* Aggressively remove things we do not need anymore from the shared memory. *)
let ast_environment = TypeEnvironment.ast_environment environment in
AstEnvironment.remove_sources ast_environment qualifiers;
Memory.SharedMemory.collect `aggressive;
()
let analyze
~scheduler
~analysis
~static_analysis_configuration
~cache
~filename_lookup
~environment
~qualifiers
~initial_callables:{ callables_with_dependency_information; stubs; filtered_callables; _ }
~initial_models
~skip_overrides
()
=
Log.info "Recording initial models in shared memory...";
let timer = Timer.start () in
Interprocedural.FixpointAnalysis.record_initial_models
~callables:(List.map callables_with_dependency_information ~f:fst)
~stubs
initial_models;
Statistics.performance
~name:"Recorded initial models"
~phase_name:"Recording initial models"
~timer
();
Log.info "Computing overrides...";
let timer = Timer.start () in
let { DependencyGraphSharedMemory.overrides; skipped_overrides } =
record_overrides_for_qualifiers
~scheduler
~cache
~environment:(Analysis.TypeEnvironment.read_only environment)
~skip_overrides
~qualifiers
in
let override_dependencies = DependencyGraph.from_overrides overrides in
Statistics.performance ~name:"Overrides computed" ~phase_name:"Computing overrides" ~timer ();
Log.info "Building call graph...";
let timer = Timer.start () in
let callgraph =
build_call_graph
~scheduler
~static_analysis_configuration
~cache
~environment:(Analysis.TypeEnvironment.read_only environment)
~qualifiers
in
Statistics.performance ~name:"Call graph built" ~phase_name:"Building call graph" ~timer ();
Log.info "Computing dependencies...";
let timer = Timer.start () in
let dependencies, callables_to_analyze, override_targets =
build_dependency_graph ~callables_with_dependency_information ~callgraph ~override_dependencies
in
Statistics.performance
~name:"Computed dependencies"
~phase_name:"Computing dependencies"
~timer
();
Log.info "Purging shared memory...";
let timer = Timer.start () in
let () = purge_shared_memory ~environment ~qualifiers in
Statistics.performance ~name:"Purged shared memory" ~phase_name:"Purging shared memory" ~timer ();
Log.info
"Analysis fixpoint started for %d overrides and %d functions..."
(List.length override_targets)
(List.length callables_to_analyze);
let callables_to_analyze = List.rev_append override_targets callables_to_analyze in
let fixpoint_timer = Timer.start () in
let compute_fixpoint () =
Interprocedural.FixpointAnalysis.compute_fixpoint
~scheduler
~environment:(Analysis.TypeEnvironment.read_only environment)
~analysis
~dependencies
~filtered_callables
~all_callables:callables_to_analyze
Interprocedural.FixpointState.Epoch.initial
in
let report_results fixpoint_iterations =
let callables =
Target.Set.of_list (List.rev_append (Target.Map.keys initial_models) callables_to_analyze)
in
Interprocedural.FixpointAnalysis.report_results
~scheduler
~static_analysis_configuration
~environment:(Analysis.TypeEnvironment.read_only environment)
~filename_lookup
~analysis
~callables
~skipped_overrides
~fixpoint_timer
~fixpoint_iterations
in
try
let fixpoint_iterations = compute_fixpoint () in
let summary = report_results (Some fixpoint_iterations) in
Yojson.Safe.pretty_to_string (`List summary) |> Log.print "%s"
with
| exn ->
let _ = report_results None in
raise exn