diff --git a/infer/src/checkers/Trace.ml b/infer/src/checkers/Trace.ml index 4f3acb0ce..f1db3ab07 100644 --- a/infer/src/checkers/Trace.ml +++ b/infer/src/checkers/Trace.ml @@ -30,6 +30,11 @@ module type S = sig module Sinks = Sink.Set module Passthroughs = Passthrough.Set + (** path from a source to a sink with passthroughs at each step in the call stack. the first set + of passthroughs are the ones in the "reporting" procedure that calls the first function in + both the source and sink stack *) + type path = Passthroughs.t * (Source.t * Passthroughs.t) list * (Sink.t * Passthroughs.t) list + (** get the sources of the trace. *) val sources : t -> Sources.t @@ -42,13 +47,8 @@ module type S = sig (** get the reportable source-sink flows in this trace *) val get_reports : t -> (Source.t * Sink.t * Passthroughs.t) list - (** get logging-ready trace strings for the reportable source-sink flows in this trace *) - val get_reportable_traces : - t -> - Procname.t -> - ?expand_trace:bool -> - trace_of_pname:(Procname.t -> t) -> - (Source.t * Sink.t * string) list + (** get a path for each of the reportable source -> sink flows in this trace *) + val get_reportable_paths : t -> trace_of_pname:(Procname.t -> t) -> path list (** create a trace from a source *) val of_source : Source.t -> t @@ -73,6 +73,9 @@ module type S = sig val equal : t -> t -> bool val pp : F.formatter -> t -> unit + + (** pretty-print a path in the context of the given procname *) + val pp_path : F.formatter -> Procname.t -> path -> unit end (** Expand a trace element (i.e., a source or sink) into a list of trace elements bottoming out in @@ -124,6 +127,8 @@ module Make (Spec : Spec) = struct type astate = t + type path = Passthroughs.t * (Source.t * Passthroughs.t) list * (Sink.t * Passthroughs.t) list + let compare t1 t2 = Sources.compare t1.sources t2.sources |> next Sinks.compare t1.sinks t2.sinks @@ -161,57 +166,52 @@ module Make (Spec : Spec) = struct else acc in Sources.fold (fun source acc -> Sinks.fold (report_one source) t.sinks acc) t.sources [] - let get_reportable_traces t cur_pname ?(expand_trace=true) ~trace_of_pname = + let pp_path fmt cur_pname (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 get_expanded_trace_string - cur_pname cur_passthroughs sources_passthroughs sinks_passthroughs = - 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 Source.call_site in - let pp_sinks = pp_elems Sink.call_site in - let original_source = fst (IList.hd sources_passthroughs) in - let final_sink = fst (IList.hd sinks_passthroughs) in - F.asprintf - "Error: %a -> %a. Full trace:@.%a@.Current procedure %a %a@.%a" - Source.pp original_source - Sink.pp final_sink - pp_sources sources_passthroughs - Procname.pp cur_pname - pp_passthroughs cur_passthroughs - pp_sinks (IList.rev sinks_passthroughs) in - - let get_trace_string source sink cur_passthroughs = - if expand_trace - then - let sources_of_pname pname = - let trace = trace_of_pname pname in - Sources.elements (sources trace), 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 = - SourceExpander.expand source ~elems_passthroughs_of_pname:sources_of_pname in - let sinks_passthroughs = - SinkExpander.expand sink ~elems_passthroughs_of_pname:sinks_of_pname in - get_expanded_trace_string cur_pname cur_passthroughs sources_passthroughs sinks_passthroughs - else - F.asprintf - "Error: %a -> %a %a" - Source.pp source Sink.pp sink pp_passthroughs cur_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 Source.call_site in + let pp_sinks = pp_elems Sink.call_site in + + let original_source = fst (IList.hd sources_passthroughs) in + let final_sink = fst (IList.hd sinks_passthroughs) in + F.fprintf + fmt + "Error: %a -> %a. Full trace:@.%a@.Current procedure %a %a@.%a" + Source.pp original_source + Sink.pp final_sink + pp_sources sources_passthroughs + Procname.pp cur_pname + pp_passthroughs cur_passthroughs + pp_sinks (IList.rev sinks_passthroughs) + + let get_reportable_paths t ~trace_of_pname = + let expand_path source sink = + let sources_of_pname pname = + let trace = trace_of_pname pname in + Sources.elements (sources trace), 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 = + SourceExpander.expand source ~elems_passthroughs_of_pname:sources_of_pname in + let sinks_passthroughs = + SinkExpander.expand sink ~elems_passthroughs_of_pname:sinks_of_pname in + sources_passthroughs, sinks_passthroughs in IList.map (fun (source, sink, passthroughs) -> - let trace_string = get_trace_string source sink passthroughs in - source, sink, trace_string) + let sources_passthroughs, sinks_passthroughs = expand_path source sink in + passthroughs, sources_passthroughs, sinks_passthroughs) (get_reports t) let of_source source = diff --git a/infer/src/checkers/Trace.mli b/infer/src/checkers/Trace.mli index 9e1615227..e3f863591 100644 --- a/infer/src/checkers/Trace.mli +++ b/infer/src/checkers/Trace.mli @@ -30,6 +30,11 @@ module type S = sig module Sinks = Sink.Set module Passthroughs = Passthrough.Set + (** path from a source to a sink with passthroughs at each step in the call stack. the first set + of passthroughs are the ones in the "reporting" procedure that calls the first function in + both the source and sink stack *) + type path = Passthroughs.t * (Source.t * Passthroughs.t) list * (Sink.t * Passthroughs.t) list + (** get the sources of the trace. *) val sources : t -> Sources.t @@ -42,13 +47,8 @@ module type S = sig (** get the reportable source-sink flows in this trace *) val get_reports : t -> (Source.t * Sink.t * Passthroughs.t) list - (** get logging-ready trace strings for the reportable source-sink flows in this trace *) - val get_reportable_traces : - t -> - Procname.t -> - ?expand_trace:bool -> - trace_of_pname:(Procname.t -> t) -> - (Source.t * Sink.t * string) list + (** get a path for each of the reportable source -> sink flows in this trace *) + val get_reportable_paths : t -> trace_of_pname:(Procname.t -> t) -> path list (** create a trace from a source *) val of_source : Source.t -> t @@ -73,6 +73,9 @@ module type S = sig val equal : t -> t -> bool val pp : F.formatter -> t -> unit + + (** pretty-print a path in the context of the given procname *) + val pp_path : F.formatter -> Procname.t -> path -> unit end module Make (Spec : Spec) : S with module Source = Spec.Source and module Sink = Spec.Sink diff --git a/infer/src/quandary/TaintAnalysis.ml b/infer/src/quandary/TaintAnalysis.ml index a9ab9059e..2193eea40 100644 --- a/infer/src/quandary/TaintAnalysis.ml +++ b/infer/src/quandary/TaintAnalysis.ml @@ -157,20 +157,33 @@ module Make (TaintSpec : TaintSpec.S) = struct | None -> TraceDomain.initial in - let caller_pname = Cfg.Procdesc.get_proc_name proc_data.pdesc in - let expand_trace = false in - match TraceDomain.get_reportable_traces trace caller_pname ~expand_trace ~trace_of_pname with + let pp_path_short 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 source = fst (IList.hd (IList.rev sources_passthroughs)) in + let sink = fst (IList.hd (IList.rev sinks_passthroughs)) in + F.fprintf + fmt + "Error: %a -> %a %a" + TraceDomain.Source.pp source + TraceDomain.Sink.pp sink + pp_passthroughs cur_passthroughs in + + match TraceDomain.get_reportable_paths trace ~trace_of_pname with | [] -> trace - | reportable_trace_strs -> + | paths -> + let caller_pname = Cfg.Procdesc.get_proc_name proc_data.pdesc in let reported_sinks = IList.map - (fun (_, sink, trace_str) -> + (fun ((_, _, sinks) as path) -> let msg = Localise.to_string Localise.quandary_taint_error in + let trace_str = F.asprintf "%a" pp_path_short path in let exn = Exceptions.Checkers (msg, Localise.verbatim_desc trace_str) in Reporting.log_error caller_pname ~loc:(CallSite.loc callee_site) exn; - sink) - reportable_trace_strs in + fst (IList.hd (IList.rev sinks))) + paths in (* got new source -> sink flow. report it, but don't add the sink to the trace. if we do, we will double-report later on. *) TraceDomain.filter_sinks trace reported_sinks