(* * 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. *) open! Utils module F = Format module L = Logging module type Spec = sig module Source : Source.S module Sink : Sink.S (** should a flow originating at source and entering sink be reported? *) val should_report : Source.t -> Sink.t -> bool (** get a loggable exception reporting a flow from source -> sink *) val get_reportable_exn : Source.t -> Sink.t -> Passthrough.Set.t -> exn end module type S = sig include Spec type t type astate = t include AbstractDomain.S with type astate := astate module Sources = Source.Set module Sinks = Sink.Set module Passthroughs = Passthrough.Set (** get the sources of the trace. *) val sources : t -> Sources.t (** get the sinks of the trace *) val sinks : t -> Sinks.t (** get the passthroughs of the trace *) val passthroughs : t -> Passthroughs.t (** get the reportable source-sink flows in this trace *) val get_reports : t -> (Source.t * Sink.t * Passthroughs.t) list (** get logging-ready exceptions for the reportable source-sink flows in this trace *) val get_reportable_exns : t -> exn list (** create a trace from a source *) val of_source : Source.t -> t (** ad a source to the current trace *) val add_source : Source.t -> t -> t (** add a sink to the current trace. *) val add_sink : Sink.t -> t -> t (** append the trace for given call site to the current caller trace *) val append : t -> t -> CallSite.t -> t (** return true if this trace has no source or sink data *) val is_empty : t -> bool val compare : t -> t -> int val equal : t -> t -> bool val pp : F.formatter -> t -> unit end module Make (Spec : Spec) = struct include Spec module Sources = Source.Set module Sinks = Sink.Set module Passthroughs = Passthrough.Set 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 astate = t let compare t1 t2 = Sources.compare t1.sources t2.sources |> next Sinks.compare t1.sinks t2.sinks |> next Passthroughs.compare t1.passthroughs t2.passthroughs let equal t1 t2 = compare t1 t2 = 0 let pp fmt t = F.fprintf fmt "%a -> %a via %a" Sources.pp t.sources Sinks.pp t.sinks Passthroughs.pp t.passthroughs let sources t = t.sources let sinks t = t.sinks let passthroughs t = t.passthroughs let is_empty t = (* sources empty => sinks empty and passthroughs empty *) Sources.is_empty t.sources let get_reports t = if Sinks.is_empty t.sinks then [] else let report_one source sink acc = if Spec.should_report source sink then (source, sink, t.passthroughs) :: acc else acc in Sources.fold (fun source acc -> Sinks.fold (report_one source) t.sinks acc) t.sources [] let get_reportable_exns t = IList.map (fun (source, sink, passthroughs) -> Spec.get_reportable_exn source sink passthroughs) (get_reports t) let of_source source = let sources = Sources.singleton source in let passthroughs = Passthroughs.empty in let sinks = Sinks.empty in { sources; passthroughs; sinks; } 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; } (** compute caller_trace + callee_trace *) let append caller_trace callee_trace callee_site = if is_empty callee_trace then caller_trace else let sources = Sources.filter (fun source -> not (Source.is_footprint source)) callee_trace.sources |> Sources.union caller_trace.sources in let sinks = Sinks.union caller_trace.sinks callee_trace.sinks in let passthroughs = let is_original_sink sink = Procname.equal (CallSite.pname callee_site) (CallSite.pname (Sink.call_site sink)) in (* add [callee_site] as a passthrough if it is not an "original" sink; that is, a procedure that is itself a sink rather than a caller of a sink *) if Sinks.exists is_original_sink callee_trace.sinks then caller_trace.passthroughs else Passthroughs.add (Passthrough.make callee_site) caller_trace.passthroughs in { sources; sinks; passthroughs; } let initial = let sources = Sources.empty in let sinks = Sinks.empty in let passthroughs = Passthroughs.empty in { sources; sinks; passthroughs; } let (<=) ~lhs ~rhs = lhs == rhs || (Sources.subset lhs.sources rhs.sources && Sinks.subset lhs.sinks rhs.sinks && Passthroughs.subset lhs.passthroughs rhs.passthroughs) let join t1 t2 = if t1 == t2 then t1 else let sources = Sources.union 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:_ = join prev next end