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.

189 lines
5.4 KiB

(*
* 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