Skip to content

Commit

Permalink
Extract/unify dependency tracking into its own module under IR
Browse files Browse the repository at this point in the history
Summary:
Instead of tracking dependencies separately in `Summary` and `Tenv` (and potentially soon-to-be
`Attributes`), keep it all in one place under the IR module.  Though not "IR" technically, this is a
good place for dependency-tracking to live so that it has access to Procnames and SourceFiles but is
maximally available in other places where dependencies need to be recorded.

Reviewed By: ngorogiannis

Differential Revision: D43569089

fbshipit-source-id: bc7823b407211826c7a05c8ef5fe33c198cc1775
  • Loading branch information
bennostein authored and facebook-github-bot committed Feb 27, 2023
1 parent 432cbbd commit a0bfdbb
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 156 deletions.
71 changes: 71 additions & 0 deletions infer/src/IR/Dependencies.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
(*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
module L = Logging

let currently_under_analysis : Procname.t option ref = ref None

let set_current_proc pname = currently_under_analysis := pname

let get_current_proc () = !currently_under_analysis

type partial = Procname.HashSet.t

type complete = {callees: Procname.t list; used_tenv_sources: SourceFile.t list}

type t = Partial of partial | Complete of complete

let deps_in_progress : (SourceFile.HashSet.t * Procname.HashSet.t) Procname.Hash.t =
Procname.Hash.create 0


let reset pname =
let pname_deps = Procname.HashSet.create 0 in
let srcfile_deps = SourceFile.HashSet.create 0 in
Procname.Hash.replace deps_in_progress pname (srcfile_deps, pname_deps) ;
Partial pname_deps


let freeze pname deps =
match deps with
| Partial _ ->
let srcfile_deps, pname_deps = Procname.Hash.find deps_in_progress pname in
(* Remove recorded dependencies for this [pname] to save memory, since they are read at most once. *)
Procname.Hash.remove deps_in_progress pname ;
let callees = Iter.to_list (Procname.HashSet.iter pname_deps) in
let used_tenv_sources = Iter.to_list (SourceFile.HashSet.iter srcfile_deps) in
{callees; used_tenv_sources}
| Complete c ->
c


let complete_exn = function
| Complete c ->
c
| Partial _ ->
L.die InternalError "complete dependency info unavailable for partially-computed summary"


let record_pname_dep ?caller callee =
let caller = match caller with Some _ -> caller | None -> !currently_under_analysis in
match caller with
| Some caller when not (Procname.equal caller callee) ->
Option.iter (Procname.Hash.find_opt deps_in_progress caller) ~f:(fun (_, caller_pname_deps) ->
Procname.HashSet.add callee caller_pname_deps )
| _ ->
()


let record_srcfile_dep src_file =
Option.iter !currently_under_analysis ~f:(fun pname ->
let deps, _ = Procname.Hash.find deps_in_progress pname in
SourceFile.HashSet.add src_file deps )


let clear () =
Procname.Hash.clear deps_in_progress ;
currently_under_analysis := None
46 changes: 46 additions & 0 deletions infer/src/IR/Dependencies.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
(*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)

open! IStd

type partial = Procname.HashSet.t

type complete = {callees: Procname.t list; used_tenv_sources: SourceFile.t list}

type t =
| Partial of partial
| Complete of complete
(** Dependencies are [partial] and mutable while the summary to which they belong is being
computed, then made [complete] and immutable once the summary is fully analyzed. *)

(** Mutable state keeping track during on-demand interprocedural analysis of (1) which procedure is
currently being analyzed and (2) which procedures type environments were used to compute
summaries.
Located here in the IR module to avoid adding parameters threading the currently-under-analysis
procedure throughout various analysis engine and checker code. These dependencies are then used
in the Backend module to conservatively invalidate procedure summaries that were computed using
out-of-date type environment information. *)

val reset : Procname.t -> t

val freeze : Procname.t -> t -> complete

val complete_exn : t -> complete

val set_current_proc : Procname.t option -> unit
(** set (or unset) the currently-under-analysis procedure *)

val get_current_proc : unit -> Procname.t option
(** get the currently-under-analysis procedure if one exists *)

val record_pname_dep : ?caller:Procname.t -> Procname.t -> unit

val record_srcfile_dep : SourceFile.t -> unit

val clear : unit -> unit
(** drop all currently-recorded dependency edges to reclaim memory *)
65 changes: 5 additions & 60 deletions infer/src/IR/Tenv.ml
Original file line number Diff line number Diff line change
Expand Up @@ -6,63 +6,13 @@
*)
open! IStd
module L = Logging
open Option.Monad_infix

(** Module for Type Environments. *)

(** Hash tables on type names. *)
module TypenameHash = Caml.Hashtbl.Make (Typ.Name)

(** Mutable state keeping track of which procedure summaries depend on which type environments. *)
module Deps : sig
val set_current_proc : Procname.t option -> unit
(** set (or unset) the currently-under-analysis procedure *)

val get_current_proc : unit -> Procname.t option
(** get the currently-under-analysis procedure if one exists *)

val of_procname : Procname.t -> SourceFile.t list
(** Return a list of source files whose type environments were used to compute a summary of the
given [proc_name], and drop that set from the global dependency map to reclaim some memory. *)

val record : Procname.t -> SourceFile.t -> unit
(** Record a dependency from a [proc_name] upon the type environment of some [source_file] *)

val clear : unit -> unit
(** drop all currently-recorded dependency edges to reclaim memory *)
end = struct
let currently_under_analysis : Procname.t option ref = ref None

let recorded_dependencies : SourceFile.HashSet.t Procname.Hash.t = Procname.Hash.create 1000

let set_current_proc = ( := ) currently_under_analysis

let get_current_proc () = !currently_under_analysis

let of_procname pname =
match Procname.Hash.find_opt recorded_dependencies pname with
| Some deps ->
(* Remove recorded dependencies for this [pname] to save memory, since they are read at most once. *)
Procname.Hash.remove recorded_dependencies pname ;
SourceFile.HashSet.iter deps |> Iter.to_list
| None ->
[]


let record pname src_file =
SourceFile.HashSet.add src_file
@@
match Procname.Hash.find_opt recorded_dependencies pname with
| Some deps ->
deps
| None ->
let deps = SourceFile.HashSet.create 0 in
Procname.Hash.add recorded_dependencies pname deps ;
deps


let clear () = Procname.Hash.clear recorded_dependencies
end

(** Type for type environment. *)
type t = Struct.t TypenameHash.t

Expand Down Expand Up @@ -100,9 +50,7 @@ let lookup tenv name : Struct.t option =
None )
in
(* Record Tenv lookups during analysis to facilitate conservative incremental invalidation *)
IOption.iter2 (Deps.get_current_proc ())
(Option.bind result ~f:Struct.get_source_file)
~f:Deps.record ;
Option.iter (result >>= Struct.get_source_file) ~f:Dependencies.record_srcfile_dep ;
result


Expand Down Expand Up @@ -245,9 +193,8 @@ let load source =
|> Sqlite3.bind load_stmt 1
|> SqliteUtils.check_result_code db ~log:"load bind source file" ;
SqliteUtils.result_single_column_option ~finalize:false ~log:"Tenv.load" db load_stmt
|> Option.bind ~f:(fun x ->
SQLite.deserialize x
|> function Global -> load_global () | FileLocal tenv -> Some tenv ) )
>>| SQLite.deserialize
>>= function Global -> load_global () | FileLocal tenv -> Some tenv )


let store_debug_file tenv tenv_filename =
Expand Down Expand Up @@ -342,9 +289,7 @@ let resolve_method ~method_exists tenv class_name proc_name =
[]
in
List.find_map supers_to_search ~f:resolve_name )
and resolve_name class_name =
lookup tenv class_name |> Option.bind ~f:(resolve_name_struct class_name)
in
and resolve_name class_name = lookup tenv class_name >>= resolve_name_struct class_name in
resolve_name class_name


Expand Down
22 changes: 0 additions & 22 deletions infer/src/IR/Tenv.mli
Original file line number Diff line number Diff line change
Expand Up @@ -95,25 +95,3 @@ module SQLite : SqliteUtils.Data with type t = per_file

val normalize : per_file -> per_file
(** Produce an equivalent type environment that has maximal sharing between its structures. *)

(** Mutable state keeping track during on-demand interprocedural analysis of (1) which procedure is
currently being analyzed and (2) which type environments were used to compute summaries.
Located here in the IR module to track type-environment dependencies without adding parameters
to track the currently-under-analysis procedure throughout various analysis engine and checker
code. These dependencies are then used to conservatively invalidate procedure summaries that
were computed using out-of-date type environment information. *)
module Deps : sig
val set_current_proc : Procname.t option -> unit
(** set (or unset) the currently-under-analysis procedure *)

val get_current_proc : unit -> Procname.t option
(** get the currently-under-analysis procedure if one exists *)

val of_procname : Procname.t -> SourceFile.t list
(** Return a list of source files whose type environments were used to compute a summary of the
given [proc_name], and drop that set from the global dependency map to reclaim some memory. *)

val clear : unit -> unit
(** drop all currently-recorded dependency edges to reclaim memory *)
end
2 changes: 1 addition & 1 deletion infer/src/backend/AnalysisDependencyGraph.ml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ let build ~changed_files =
let tenv_deps = SourceFile.Hash.create 0 in
(* First, build a reverse analysis callgraph [graph] and tenv dependency map [tenv_deps]. *)
Summary.OnDisk.iter_specs ~f:(fun {Summary.proc_name; dependencies} ->
let Summary.Deps.{callees; used_tenv_sources} =
let Dependencies.{callees; used_tenv_sources} =
match dependencies with
| Complete c ->
c
Expand Down
2 changes: 1 addition & 1 deletion infer/src/backend/InferAnalyze.ml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ let clear_caches () =
Summary.OnDisk.clear_cache () ;
BufferOverrunUtils.clear_cache () ;
Attributes.clear_cache () ;
Tenv.Deps.clear ()
Dependencies.clear ()


let proc_name_of_uid uid =
Expand Down
49 changes: 7 additions & 42 deletions infer/src/backend/Summary.ml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

open! IStd
module F = Format
module L = Logging
module BStats = Stats

module Stats = struct
Expand Down Expand Up @@ -42,48 +41,13 @@ module Stats = struct
F.fprintf fmt "FAILURE:%a SYMOPS:%d@\n" pp_failure_kind_opt failure_kind symops
end

module Deps = struct
type partial = Procname.HashSet.t

type complete = {callees: Procname.t list; used_tenv_sources: SourceFile.t list}

type t = Partial of partial | Complete of complete

let empty () = Partial (Procname.HashSet.create 0)

let freeze pname = function
| Partial callees ->
let callees = Iter.to_list (Procname.HashSet.iter callees) in
let used_tenv_sources = Tenv.Deps.of_procname pname in
{callees; used_tenv_sources}
| Complete deps ->
deps


let partial_exn = function
| Partial p ->
p
| Complete _ ->
L.die InternalError "partial dependency info unavailable for completed summary"


let complete_exn = function
| Complete c ->
c
| Partial _ ->
L.die InternalError "complete depenendency info unavailable for partially-computed summary"


let add_exn deps callee = Procname.HashSet.add callee (partial_exn deps)
end

type t =
{ payloads: Payloads.t
; mutable sessions: int
; stats: Stats.t
; proc_name: Procname.t
; err_log: Errlog.t
; mutable dependencies: Deps.t }
; mutable dependencies: Dependencies.t }

let yojson_of_t {proc_name; payloads} = [%yojson_of: Procname.t * Payloads.t] (proc_name, payloads)

Expand Down Expand Up @@ -140,14 +104,15 @@ module ReportSummary = struct
end

module SummaryMetadata = struct
type t = {sessions: int; stats: Stats.t; proc_name: Procname.t; dependencies: Deps.complete}
type t =
{sessions: int; stats: Stats.t; proc_name: Procname.t; dependencies: Dependencies.complete}
[@@deriving fields]

let of_full_summary (f : full_summary) : t =
{ sessions= f.sessions
; stats= f.stats
; proc_name= f.proc_name
; dependencies= Deps.complete_exn f.dependencies }
; dependencies= Dependencies.complete_exn f.dependencies }


module SQLite = SqliteUtils.MarshalledDataNOTForComparison (struct
Expand All @@ -161,7 +126,7 @@ let mk_full_summary payloads (report_summary : ReportSummary.t)
; sessions= summary_metadata.sessions
; stats= summary_metadata.stats
; proc_name= summary_metadata.proc_name
; dependencies= Deps.Complete summary_metadata.dependencies
; dependencies= Dependencies.Complete summary_metadata.dependencies
; err_log= report_summary.err_log }


Expand Down Expand Up @@ -261,7 +226,7 @@ module OnDisk = struct
let store ({proc_name; dependencies; payloads} as summary : t) =
(* Make sure the summary in memory is identical to the saved one *)
add proc_name summary ;
summary.dependencies <- Deps.(Complete (freeze proc_name dependencies)) ;
summary.dependencies <- Dependencies.(Complete (freeze proc_name dependencies)) ;
let report_summary = ReportSummary.of_full_summary summary in
let summary_metadata = SummaryMetadata.of_full_summary summary in
DBWriter.store_spec ~proc_uid:(Procname.to_unique_id proc_name)
Expand All @@ -278,7 +243,7 @@ module OnDisk = struct
; stats= Stats.empty
; proc_name
; err_log= Errlog.empty ()
; dependencies= Deps.empty () }
; dependencies= Dependencies.reset proc_name }
in
Procname.Hash.replace cache proc_name summary ;
summary
Expand Down
Loading

0 comments on commit a0bfdbb

Please sign in to comment.