You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
573 lines
21 KiB
573 lines
21 KiB
(*
|
|
* Copyright (c) 2016-present, Facebook, Inc.
|
|
*
|
|
* 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 F = Format
|
|
|
|
module type Spec = sig
|
|
module Source : Source.S
|
|
|
|
module Sink : Sink.S
|
|
|
|
module Sanitizer : Sanitizer.S
|
|
|
|
val get_report : Source.t -> Sink.t -> Sanitizer.t list -> IssueType.t option
|
|
end
|
|
|
|
module type S = sig
|
|
include Spec
|
|
|
|
include AbstractDomain.WithBottom
|
|
|
|
module Sources : sig
|
|
module Known : module type of AbstractDomain.FiniteSet (Source)
|
|
|
|
module FootprintConfig : AccessTree.Config
|
|
|
|
module Footprint : module type of AccessTree.PathSet (FootprintConfig)
|
|
|
|
module Sanitizers : module type of AbstractDomain.FiniteSet (Sanitizer)
|
|
|
|
type t = {known: Known.t; footprint: Footprint.t; sanitizers: Sanitizers.t}
|
|
|
|
val empty : t
|
|
|
|
val is_empty : t -> bool
|
|
|
|
val of_source : Source.t -> t
|
|
|
|
val of_footprint : AccessPath.Abs.t -> t
|
|
|
|
val add : Source.t -> t -> t
|
|
|
|
val get_footprint_indexes : t -> IntSet.t
|
|
end
|
|
|
|
module Sinks = Sink.Set
|
|
module Passthroughs = Passthrough.Set
|
|
|
|
type path = Passthroughs.t * (Source.t * Passthroughs.t) list * (Sink.t * Passthroughs.t) list
|
|
|
|
type report =
|
|
{ issue: IssueType.t
|
|
; path_source: Source.t
|
|
; path_sink: Sink.t
|
|
; path_passthroughs: Passthroughs.t }
|
|
|
|
val sources : t -> Sources.t
|
|
(** get the sources of the trace. *)
|
|
|
|
val sinks : t -> Sinks.t
|
|
(** get the sinks of the trace *)
|
|
|
|
val passthroughs : t -> Passthroughs.t
|
|
(** get the passthroughs of the trace *)
|
|
|
|
val get_reports : ?cur_site:CallSite.t -> t -> report list
|
|
(** get the reportable source-sink flows in this trace. specifying [cur_site] restricts the
|
|
reported paths to ones introduced by the call at [cur_site] *)
|
|
|
|
val get_reportable_paths :
|
|
?cur_site:CallSite.t -> t -> trace_of_pname:(Typ.Procname.t -> t) -> path list
|
|
(** get a path for each of the reportable source -> sink flows in this trace. specifying
|
|
[cur_site] restricts the reported paths to ones introduced by the call at [cur_site] *)
|
|
|
|
val to_loc_trace :
|
|
?desc_of_source:(Source.t -> string)
|
|
-> ?source_should_nest:(Source.t -> bool)
|
|
-> ?desc_of_sink:(Sink.t -> string)
|
|
-> ?sink_should_nest:(Sink.t -> bool)
|
|
-> path
|
|
-> Errlog.loc_trace
|
|
(** create a loc_trace from a path; [source_should_nest s] should be true when we are going one
|
|
deeper into a call-chain, ie when lt_level should be bumper in the next loc_trace_elem, and
|
|
similarly for [sink_should_nest] *)
|
|
|
|
val of_source : Source.t -> t
|
|
(** create a trace from a source *)
|
|
|
|
val of_footprint : AccessPath.Abs.t -> t
|
|
(** create a trace from a footprint access path *)
|
|
|
|
val add_source : Source.t -> t -> t
|
|
(** add a source to the current trace *)
|
|
|
|
val add_sink : Sink.t -> t -> t
|
|
(** add a sink to the current trace. *)
|
|
|
|
val add_sanitizer : Sanitizer.t -> t -> t
|
|
(** add a sanitizer to the current trace *)
|
|
|
|
val update_sources : t -> Sources.t -> t
|
|
|
|
val update_sinks : t -> Sinks.t -> t
|
|
|
|
val get_footprint_indexes : t -> IntSet.t
|
|
|
|
val append : t -> t -> CallSite.t -> t
|
|
(** append the trace for given call site to the current caller trace *)
|
|
|
|
val pp : F.formatter -> t -> unit
|
|
|
|
val pp_path : Typ.Procname.t -> F.formatter -> path -> unit
|
|
(** pretty-print a path in the context of the given procname *)
|
|
end
|
|
|
|
(** Expand a trace element (i.e., a source or sink) into a list of trace elements bottoming out in
|
|
the "original" trace element. The list is always non-empty. *)
|
|
module Expander (TraceElem : TraceElem.S) = struct
|
|
let expand elem0 ~elems_passthroughs_of_pname ~filter_passthroughs =
|
|
let rec expand_ elem (elems_passthroughs_acc, seen_acc) =
|
|
let caller_elem_site = TraceElem.call_site elem in
|
|
let caller_elem_kind = TraceElem.kind elem in
|
|
let seen_acc' = CallSite.Set.add caller_elem_site seen_acc in
|
|
let elems, passthroughs = elems_passthroughs_of_pname (CallSite.pname caller_elem_site) in
|
|
let is_recursive callee_elem seen =
|
|
CallSite.Set.mem (TraceElem.call_site callee_elem) seen
|
|
in
|
|
(* find sinks that are the same kind as the caller, but have a different procname *)
|
|
let matching_elems =
|
|
List.filter
|
|
~f:(fun callee_elem ->
|
|
TraceElem.Kind.matches ~caller:caller_elem_kind ~callee:(TraceElem.kind callee_elem)
|
|
&& not (is_recursive callee_elem seen_acc') )
|
|
elems
|
|
in
|
|
(* arbitrarily pick one elem and explore it further *)
|
|
match matching_elems with
|
|
| callee_elem :: _ ->
|
|
(* TODO: pick the shortest path to a sink here instead (t14242809) *)
|
|
let filtered_passthroughs =
|
|
filter_passthroughs caller_elem_site (TraceElem.call_site callee_elem) passthroughs
|
|
in
|
|
expand_ callee_elem ((elem, filtered_passthroughs) :: elems_passthroughs_acc, seen_acc')
|
|
| _ ->
|
|
((elem, Passthrough.Set.empty) :: elems_passthroughs_acc, seen_acc')
|
|
in
|
|
fst (expand_ elem0 ([], CallSite.Set.empty))
|
|
end
|
|
|
|
module Make (Spec : Spec) = struct
|
|
include Spec
|
|
|
|
module Sources = struct
|
|
module Known = AbstractDomain.FiniteSet (Source)
|
|
module FootprintConfig = AccessTree.DefaultConfig
|
|
module Footprint = AccessTree.PathSet (FootprintConfig)
|
|
module Sanitizers = AbstractDomain.FiniteSet (Sanitizer)
|
|
|
|
type t = {known: Known.t; footprint: Footprint.t; sanitizers: Sanitizers.t}
|
|
|
|
let ( <= ) ~lhs ~rhs =
|
|
if phys_equal lhs rhs then true
|
|
else
|
|
Known.( <= ) ~lhs:lhs.known ~rhs:rhs.known
|
|
&& Footprint.( <= ) ~lhs:lhs.footprint ~rhs:rhs.footprint
|
|
&& Sanitizers.( <= ) ~lhs:lhs.sanitizers ~rhs:rhs.sanitizers
|
|
|
|
|
|
let join astate1 astate2 =
|
|
if phys_equal astate1 astate2 then astate1
|
|
else
|
|
let known = Known.join astate1.known astate2.known in
|
|
let footprint = Footprint.join astate1.footprint astate2.footprint in
|
|
let sanitizers = Sanitizers.join astate1.sanitizers astate2.sanitizers in
|
|
{known; footprint; sanitizers}
|
|
|
|
|
|
let widen ~prev ~next ~num_iters =
|
|
if phys_equal prev next then prev
|
|
else
|
|
let known = Known.widen ~prev:prev.known ~next:next.known ~num_iters in
|
|
let footprint = Footprint.widen ~prev:prev.footprint ~next:next.footprint ~num_iters in
|
|
let sanitizers = Sanitizers.widen ~prev:prev.sanitizers ~next:next.sanitizers ~num_iters in
|
|
{known; footprint; sanitizers}
|
|
|
|
|
|
let pp fmt {known; footprint; sanitizers} =
|
|
let pp_sanitizers fmt sanitizers =
|
|
if not (Sanitizers.is_empty sanitizers) then
|
|
F.fprintf fmt " + Sanitizers(%a)" Sanitizers.pp sanitizers
|
|
in
|
|
if Known.is_empty known then
|
|
if Footprint.is_bottom footprint then F.pp_print_string fmt "{}"
|
|
else F.fprintf fmt "Footprint(%a)%a" Footprint.pp footprint pp_sanitizers sanitizers
|
|
else
|
|
F.fprintf fmt "%a + Footprint(%a)%a" Known.pp known Footprint.pp footprint pp_sanitizers
|
|
sanitizers
|
|
|
|
|
|
let empty = {known= Known.empty; footprint= Footprint.bottom; sanitizers= Sanitizers.empty}
|
|
|
|
(* note: empty known/footprint implies empty sanitizers *)
|
|
let is_empty {known; footprint} = Known.is_empty known && Footprint.is_bottom footprint
|
|
|
|
let of_footprint access_path =
|
|
let footprint = Footprint.add_trace access_path true Footprint.bottom in
|
|
{empty with footprint}
|
|
|
|
|
|
let of_source source =
|
|
let known = Known.singleton source in
|
|
{empty with known}
|
|
|
|
|
|
let add source astate =
|
|
let known = Known.add source astate.known in
|
|
{astate with known}
|
|
|
|
|
|
let get_footprint_indexes {footprint} =
|
|
Footprint.BaseMap.fold
|
|
(fun base _ acc ->
|
|
match AccessPath.Abs.get_footprint_index_base base with
|
|
| Some footprint_index ->
|
|
IntSet.add footprint_index acc
|
|
| None ->
|
|
acc )
|
|
footprint IntSet.empty
|
|
end
|
|
|
|
module Sinks = Sink.Set
|
|
module Passthroughs = Passthrough.Set
|
|
module SourceExpander = Expander (Source)
|
|
module SinkExpander = Expander (Sink)
|
|
|
|
type t =
|
|
{ sources: Sources.t (** last functions in the trace that returned tainted data *)
|
|
; sinks: Sinks.t
|
|
(** last callees in the trace that transitively called a tainted function (if any) *)
|
|
; passthroughs: Passthrough.Set.t (** calls that occurred between source and sink *) }
|
|
|
|
type path = Passthroughs.t * (Source.t * Passthroughs.t) list * (Sink.t * Passthroughs.t) list
|
|
|
|
type report =
|
|
{ issue: IssueType.t
|
|
; path_source: Source.t
|
|
; path_sink: Sink.t
|
|
; path_passthroughs: Passthroughs.t }
|
|
|
|
let pp fmt {sources; sinks; passthroughs} =
|
|
let pp_passthroughs fmt passthroughs =
|
|
if not (Passthroughs.is_empty passthroughs) then
|
|
F.fprintf fmt " via %a" Passthroughs.pp passthroughs
|
|
in
|
|
let pp_sinks fmt sinks =
|
|
if Sinks.is_empty sinks then F.pp_print_char fmt '?' else Sinks.pp fmt sinks
|
|
in
|
|
(* empty sources implies empty sinks and passthroughs *)
|
|
F.fprintf fmt "%a ~> %a%a" Sources.pp sources pp_sinks sinks pp_passthroughs passthroughs
|
|
|
|
|
|
let get_path_source_call_site = Source.call_site
|
|
|
|
let sources t = t.sources
|
|
|
|
let sinks t = t.sinks
|
|
|
|
let passthroughs t = t.passthroughs
|
|
|
|
let is_bottom t =
|
|
(* sources empty => sinks empty and passthroughs empty *)
|
|
Sources.is_empty t.sources
|
|
|
|
|
|
let get_reports ?cur_site t =
|
|
if Sinks.is_empty t.sinks || Sources.is_empty t.sources then []
|
|
else
|
|
let should_report_at_site source sink =
|
|
match cur_site with
|
|
| None ->
|
|
true
|
|
| Some call_site ->
|
|
(* report when: (1) [cur_site] introduces the sink, and (2) [cur_site] does not also
|
|
introduce the source. otherwise, we'll report paths that don't respect control
|
|
flow. *)
|
|
CallSite.equal call_site (Sink.call_site sink)
|
|
&& not (CallSite.equal call_site (Source.call_site source))
|
|
in
|
|
(* written to avoid closure allocations in hot code. change with caution. *)
|
|
let report_source source sinks acc0 =
|
|
let report_one sink acc =
|
|
if should_report_at_site source sink then
|
|
match
|
|
Spec.get_report source sink (Sources.Sanitizers.elements t.sources.sanitizers)
|
|
with
|
|
| Some issue ->
|
|
{issue; path_source= source; path_sink= sink; path_passthroughs= t.passthroughs}
|
|
:: acc
|
|
| None ->
|
|
acc
|
|
else acc
|
|
in
|
|
Sinks.fold report_one sinks acc0
|
|
in
|
|
let report_sources source acc = report_source source t.sinks acc in
|
|
Sources.Known.fold report_sources t.sources.known []
|
|
|
|
|
|
let pp_path_source = Source.pp
|
|
|
|
let pp_path cur_pname fmt (cur_passthroughs, sources_passthroughs, sinks_passthroughs) =
|
|
let pp_passthroughs fmt passthroughs =
|
|
if not (Passthrough.Set.is_empty passthroughs) then
|
|
F.fprintf fmt "(via %a)" Passthrough.Set.pp passthroughs
|
|
in
|
|
let pp_elems elem_to_callsite fmt elems_passthroughs =
|
|
let pp_sep fmt () = F.fprintf fmt "@." in
|
|
let pp_elem fmt (elem, passthroughs) =
|
|
F.fprintf fmt "|=> %a %a" CallSite.pp (elem_to_callsite elem) pp_passthroughs passthroughs
|
|
in
|
|
F.pp_print_list ~pp_sep pp_elem fmt elems_passthroughs
|
|
in
|
|
let pp_sources = pp_elems get_path_source_call_site in
|
|
let pp_sinks = pp_elems Sink.call_site in
|
|
let original_source = fst (List.hd_exn sources_passthroughs) in
|
|
let final_sink = fst (List.hd_exn sinks_passthroughs) in
|
|
F.fprintf fmt "Error: %a -> %a. Full trace:@.%a@.Current procedure %a %a@.%a" pp_path_source
|
|
original_source Sink.pp final_sink pp_sources sources_passthroughs Typ.Procname.pp cur_pname
|
|
pp_passthroughs cur_passthroughs pp_sinks (List.rev sinks_passthroughs)
|
|
|
|
|
|
type passthrough_kind =
|
|
| Source (** passthroughs of a source *)
|
|
| Sink (** passthroughs of a sink *)
|
|
| Top_level (** passthroughs of a top-level source->sink path *)
|
|
|
|
let get_reportable_paths ?cur_site t ~trace_of_pname =
|
|
let filter_passthroughs_ passthrough_kind start_site end_site passthroughs =
|
|
let line_number call_site = (CallSite.loc call_site).Location.line in
|
|
let start_line = line_number start_site in
|
|
let end_line = line_number end_site in
|
|
let between_start_and_end passthrough =
|
|
let passthrough_line = line_number (Passthrough.site passthrough) in
|
|
match passthrough_kind with
|
|
| Source ->
|
|
passthrough_line >= end_line
|
|
| Sink ->
|
|
passthrough_line <= end_line
|
|
| Top_level ->
|
|
passthrough_line >= start_line && passthrough_line <= end_line
|
|
in
|
|
Passthrough.Set.filter between_start_and_end passthroughs
|
|
in
|
|
let expand_path source sink =
|
|
let sources_of_pname pname =
|
|
let trace = trace_of_pname pname in
|
|
(Sources.Known.elements (sources trace).known, passthroughs trace)
|
|
in
|
|
let sinks_of_pname pname =
|
|
let trace = trace_of_pname pname in
|
|
(Sinks.elements (sinks trace), passthroughs trace)
|
|
in
|
|
let sources_passthroughs =
|
|
let filter_passthroughs = filter_passthroughs_ Source in
|
|
SourceExpander.expand source ~elems_passthroughs_of_pname:sources_of_pname
|
|
~filter_passthroughs
|
|
|> List.map ~f:(fun (source, passthrough) -> (source, passthrough))
|
|
in
|
|
let sinks_passthroughs =
|
|
let filter_passthroughs = filter_passthroughs_ Sink in
|
|
SinkExpander.expand sink ~elems_passthroughs_of_pname:sinks_of_pname ~filter_passthroughs
|
|
in
|
|
(sources_passthroughs, sinks_passthroughs)
|
|
in
|
|
List.map
|
|
~f:(fun {path_source; path_sink; path_passthroughs} ->
|
|
let sources_passthroughs, sinks_passthroughs = expand_path path_source path_sink in
|
|
let filtered_passthroughs =
|
|
let source_site = Source.call_site path_source in
|
|
filter_passthroughs_ Top_level source_site (Sink.call_site path_sink) path_passthroughs
|
|
in
|
|
(filtered_passthroughs, sources_passthroughs, sinks_passthroughs) )
|
|
(get_reports ?cur_site t)
|
|
|
|
|
|
let to_loc_trace
|
|
?(desc_of_source =
|
|
fun source ->
|
|
let callsite = Source.call_site source in
|
|
Format.asprintf "return from %a" Typ.Procname.pp (CallSite.pname callsite))
|
|
?(source_should_nest = fun _ -> true)
|
|
?(desc_of_sink =
|
|
fun sink ->
|
|
let callsite = Sink.call_site sink in
|
|
Format.asprintf "call to %a" Typ.Procname.pp (CallSite.pname callsite))
|
|
?(sink_should_nest = fun _ -> true) (passthroughs, sources, sinks) =
|
|
let trace_elems_of_passthroughs lt_level passthroughs acc0 =
|
|
let trace_elem_of_passthrough passthrough acc =
|
|
let passthrough_site = Passthrough.site passthrough in
|
|
let desc =
|
|
F.asprintf "flow through %a" Typ.Procname.pp (CallSite.pname passthrough_site)
|
|
in
|
|
Errlog.make_trace_element lt_level (CallSite.loc passthrough_site) desc [] :: acc
|
|
in
|
|
(* sort passthroughs by ascending line number to create a coherent trace *)
|
|
let sorted_passthroughs =
|
|
List.sort
|
|
~compare:(fun passthrough1 passthrough2 ->
|
|
let loc1 = CallSite.loc (Passthrough.site passthrough1) in
|
|
let loc2 = CallSite.loc (Passthrough.site passthrough2) in
|
|
Int.compare loc1.Location.line loc2.Location.line )
|
|
(Passthroughs.elements passthroughs)
|
|
in
|
|
List.fold_right ~f:trace_elem_of_passthrough sorted_passthroughs ~init:acc0
|
|
in
|
|
let get_nesting should_nest elems start_nesting =
|
|
let level = ref start_nesting in
|
|
let get_nesting_ ((elem, _) as pair) =
|
|
if should_nest elem then incr level ;
|
|
(pair, !level)
|
|
in
|
|
List.map ~f:get_nesting_ (List.rev elems)
|
|
in
|
|
let trace_elems_of_path_elem call_site desc ~is_source ((elem, passthroughs), lt_level) acc =
|
|
let desc = desc elem in
|
|
let loc = CallSite.loc (call_site elem) in
|
|
if is_source then
|
|
let trace_elem = Errlog.make_trace_element lt_level loc desc [] in
|
|
trace_elems_of_passthroughs (lt_level + 1) passthroughs (trace_elem :: acc)
|
|
else
|
|
let trace_elem = Errlog.make_trace_element (lt_level - 1) loc desc [] in
|
|
trace_elem :: trace_elems_of_passthroughs lt_level passthroughs acc
|
|
in
|
|
let trace_elems_of_source =
|
|
trace_elems_of_path_elem get_path_source_call_site desc_of_source ~is_source:true
|
|
in
|
|
let trace_elems_of_sink =
|
|
trace_elems_of_path_elem Sink.call_site desc_of_sink ~is_source:false
|
|
in
|
|
let sources_with_level = get_nesting source_should_nest sources (-1) in
|
|
let sinks_with_level = get_nesting sink_should_nest sinks 0 in
|
|
let trace_prefix =
|
|
List.fold_right ~f:trace_elems_of_sink sinks_with_level ~init:[]
|
|
|> trace_elems_of_passthroughs 0 passthroughs
|
|
in
|
|
List.fold
|
|
~f:(fun acc source -> trace_elems_of_source source acc)
|
|
~init:trace_prefix sources_with_level
|
|
|
|
|
|
let of_sources sources =
|
|
let passthroughs = Passthroughs.empty in
|
|
let sinks = Sinks.empty in
|
|
{sources; passthroughs; sinks}
|
|
|
|
|
|
let of_source source =
|
|
let sources = Sources.of_source source in
|
|
of_sources sources
|
|
|
|
|
|
let of_footprint access_path =
|
|
let sources = Sources.of_footprint access_path in
|
|
of_sources sources
|
|
|
|
|
|
let add_source source t =
|
|
let sources = Sources.add source t.sources in
|
|
{t with sources}
|
|
|
|
|
|
let add_sink sink t =
|
|
let sinks = Sinks.add sink t.sinks in
|
|
{t with sinks}
|
|
|
|
|
|
let add_sanitizer sanitizer t =
|
|
let sanitizers = Sources.Sanitizers.add sanitizer t.sources.sanitizers in
|
|
let sources = {t.sources with sanitizers} in
|
|
{t with sources}
|
|
|
|
|
|
let update_sources t sources = {t with sources}
|
|
|
|
let update_sinks t sinks = {t with sinks}
|
|
|
|
let get_footprint_indexes trace = Sources.get_footprint_indexes trace.sources
|
|
|
|
(** compute caller_trace + callee_trace *)
|
|
let append caller_trace callee_trace callee_site =
|
|
if is_bottom callee_trace then caller_trace
|
|
else
|
|
let sanitizers =
|
|
Sources.Sanitizers.join callee_trace.sources.sanitizers caller_trace.sources.sanitizers
|
|
in
|
|
let non_footprint_callee_sources = callee_trace.sources.known in
|
|
let sources =
|
|
if Sources.Known.subset non_footprint_callee_sources caller_trace.sources.known then
|
|
if phys_equal sanitizers caller_trace.sources.sanitizers then caller_trace.sources
|
|
else {caller_trace.sources with Sources.sanitizers}
|
|
else
|
|
let known =
|
|
List.map
|
|
~f:(fun source -> Source.with_callsite source callee_site)
|
|
(Sources.Known.elements non_footprint_callee_sources)
|
|
|> Sources.Known.of_list
|
|
|> Sources.Known.union caller_trace.sources.known
|
|
in
|
|
{caller_trace.sources with Sources.known; sanitizers}
|
|
in
|
|
let sinks =
|
|
if Sinks.subset callee_trace.sinks caller_trace.sinks then caller_trace.sinks
|
|
else
|
|
let footprint_indices =
|
|
Sources.Footprint.BaseMap.fold
|
|
(fun (vname, _) _ s ->
|
|
match Var.get_footprint_index vname with Some ind -> IntSet.add ind s | None -> s
|
|
)
|
|
callee_trace.sources.footprint IntSet.empty
|
|
in
|
|
List.map
|
|
~f:(fun sink ->
|
|
Sink.with_indexes (Sink.with_callsite sink callee_site) footprint_indices )
|
|
(Sinks.elements callee_trace.sinks)
|
|
|> Sinks.of_list |> Sinks.union caller_trace.sinks
|
|
in
|
|
let passthroughs =
|
|
if Config.passthroughs then
|
|
if phys_equal sources caller_trace.sources && phys_equal sinks caller_trace.sinks then
|
|
(* this callee didn't add any new sources or any news sinks; it's just a passthrough *)
|
|
Passthroughs.add (Passthrough.make callee_site) caller_trace.passthroughs
|
|
else caller_trace.passthroughs
|
|
else Passthroughs.empty
|
|
in
|
|
{sources; sinks; passthroughs}
|
|
|
|
|
|
let bottom =
|
|
let sources = Sources.empty in
|
|
let sinks = Sinks.empty in
|
|
let passthroughs = Passthroughs.empty in
|
|
{sources; sinks; passthroughs}
|
|
|
|
|
|
let ( <= ) ~lhs ~rhs =
|
|
phys_equal lhs rhs
|
|
|| Sources.( <= ) ~lhs:lhs.sources ~rhs:rhs.sources
|
|
&& Sinks.subset lhs.sinks rhs.sinks
|
|
&& Passthroughs.subset lhs.passthroughs rhs.passthroughs
|
|
|
|
|
|
let join t1 t2 =
|
|
if phys_equal t1 t2 then t1
|
|
else
|
|
let sources = Sources.join t1.sources t2.sources in
|
|
let sinks = Sinks.union t1.sinks t2.sinks in
|
|
let passthroughs = Passthroughs.union t1.passthroughs t2.passthroughs in
|
|
{sources; sinks; passthroughs}
|
|
|
|
|
|
let widen ~prev ~next ~num_iters =
|
|
if phys_equal prev next then prev
|
|
else
|
|
let sources = Sources.widen ~prev:prev.sources ~next:next.sources ~num_iters in
|
|
let sinks = Sinks.union prev.sinks next.sinks in
|
|
let passthroughs = Passthroughs.union prev.passthroughs next.passthroughs in
|
|
{sources; sinks; passthroughs}
|
|
end
|