|
|
|
@ -15,33 +15,6 @@ module MF = MarkupFormatter
|
|
|
|
|
|
|
|
|
|
let dummy_constructor_annot = "__infer_is_constructor"
|
|
|
|
|
|
|
|
|
|
let annotation_of_str annot_str =
|
|
|
|
|
{ Annot.class_name = annot_str; parameters = []; }
|
|
|
|
|
|
|
|
|
|
(* parse user-defined specs from .inferconfig *)
|
|
|
|
|
let parse_user_defined_specs = function
|
|
|
|
|
| `List user_specs ->
|
|
|
|
|
let parse_user_spec json =
|
|
|
|
|
let open Yojson.Basic in
|
|
|
|
|
let sources = Util.member "sources" json |> Util.to_list |> List.map ~f:Util.to_string in
|
|
|
|
|
let sinks = Util.member "sink" json |> Util.to_string in
|
|
|
|
|
sources, sinks in
|
|
|
|
|
List.map ~f:parse_user_spec user_specs
|
|
|
|
|
| _ ->
|
|
|
|
|
[]
|
|
|
|
|
|
|
|
|
|
let src_snk_pairs =
|
|
|
|
|
let specs =
|
|
|
|
|
([Annotations.performance_critical], Annotations.expensive) ::
|
|
|
|
|
([Annotations.no_allocation], dummy_constructor_annot) ::
|
|
|
|
|
([Annotations.any_thread; Annotations.for_non_ui_thread], Annotations.ui_thread) ::
|
|
|
|
|
([Annotations.ui_thread; Annotations.for_ui_thread], Annotations.for_non_ui_thread) ::
|
|
|
|
|
(parse_user_defined_specs Config.annotation_reachability_custom_pairs) in
|
|
|
|
|
List.map
|
|
|
|
|
~f:(fun (src_annot_str_list, snk_annot_str) ->
|
|
|
|
|
List.map ~f:annotation_of_str src_annot_str_list, annotation_of_str snk_annot_str)
|
|
|
|
|
specs
|
|
|
|
|
|
|
|
|
|
module Domain = struct
|
|
|
|
|
module TrackingVar = AbstractDomain.FiniteSet (Var)
|
|
|
|
|
module TrackingDomain = AbstractDomain.BottomLifted (TrackingVar)
|
|
|
|
@ -253,100 +226,6 @@ let report_call_stack summary end_of_stack lookup_next_calls report call_site si
|
|
|
|
|
with Not_found -> ())
|
|
|
|
|
sink_map
|
|
|
|
|
|
|
|
|
|
let method_has_ignore_allocation_annot tenv pname =
|
|
|
|
|
check_attributes Annotations.ia_is_ignore_allocations tenv pname
|
|
|
|
|
|
|
|
|
|
(* TODO: generalize this to allow sanitizers for other annotation types, store it in [extras] so
|
|
|
|
|
we can compute it just once *)
|
|
|
|
|
let method_is_sanitizer annot tenv pname =
|
|
|
|
|
if String.equal annot.Annot.class_name dummy_constructor_annot
|
|
|
|
|
then method_has_ignore_allocation_annot tenv pname
|
|
|
|
|
else false
|
|
|
|
|
|
|
|
|
|
module TransferFunctions (CFG : ProcCfg.S) = struct
|
|
|
|
|
module CFG = CFG
|
|
|
|
|
module Domain = Domain
|
|
|
|
|
type extras = ProcData.no_extras
|
|
|
|
|
|
|
|
|
|
(* This is specific to the @NoAllocation and @PerformanceCritical checker
|
|
|
|
|
and the "unlikely" method is used to guard branches that are expected to run sufficiently
|
|
|
|
|
rarely to not affect the performances *)
|
|
|
|
|
let is_unlikely pname =
|
|
|
|
|
match pname with
|
|
|
|
|
| Typ.Procname.Java java_pname ->
|
|
|
|
|
String.equal (Typ.Procname.java_get_method java_pname) "unlikely"
|
|
|
|
|
| _ -> false
|
|
|
|
|
|
|
|
|
|
let is_tracking_exp astate = function
|
|
|
|
|
| Exp.Var id -> Domain.is_tracked_var (Var.of_id id) astate
|
|
|
|
|
| Exp.Lvar pvar -> Domain.is_tracked_var (Var.of_pvar pvar) astate
|
|
|
|
|
| _ -> false
|
|
|
|
|
|
|
|
|
|
let prunes_tracking_var astate = function
|
|
|
|
|
| Exp.BinOp (Binop.Eq, lhs, rhs)
|
|
|
|
|
when is_tracking_exp astate lhs ->
|
|
|
|
|
Exp.equal rhs Exp.one
|
|
|
|
|
| Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Eq, lhs, rhs), _)
|
|
|
|
|
when is_tracking_exp astate lhs ->
|
|
|
|
|
Exp.equal rhs Exp.zero
|
|
|
|
|
| _ ->
|
|
|
|
|
false
|
|
|
|
|
|
|
|
|
|
let check_call tenv callee_pname caller_pname call_site astate =
|
|
|
|
|
List.fold
|
|
|
|
|
~init:astate
|
|
|
|
|
~f:(fun astate (_, annot) ->
|
|
|
|
|
if method_has_annot annot tenv callee_pname &&
|
|
|
|
|
not (method_is_sanitizer annot tenv caller_pname)
|
|
|
|
|
then Domain.add_call_site annot callee_pname call_site astate
|
|
|
|
|
else astate)
|
|
|
|
|
src_snk_pairs
|
|
|
|
|
|
|
|
|
|
let merge_callee_map call_site pdesc callee_pname astate =
|
|
|
|
|
match Summary.read_summary pdesc callee_pname with
|
|
|
|
|
| None -> astate
|
|
|
|
|
| Some callee_call_map ->
|
|
|
|
|
let add_call_site annot sink calls astate =
|
|
|
|
|
if AnnotReachabilityDomain.CallSites.is_empty calls
|
|
|
|
|
then astate
|
|
|
|
|
else Domain.add_call_site annot sink call_site astate in
|
|
|
|
|
AnnotReachabilityDomain.fold
|
|
|
|
|
(fun annot sink_map astate ->
|
|
|
|
|
AnnotReachabilityDomain.SinkMap.fold
|
|
|
|
|
(add_call_site annot)
|
|
|
|
|
sink_map
|
|
|
|
|
astate)
|
|
|
|
|
callee_call_map
|
|
|
|
|
astate
|
|
|
|
|
|
|
|
|
|
let exec_instr astate { ProcData.pdesc; tenv; } _ = function
|
|
|
|
|
| Sil.Call (Some (id, _), Const (Cfun callee_pname), _, _, _)
|
|
|
|
|
when is_unlikely callee_pname ->
|
|
|
|
|
Domain.add_tracking_var (Var.of_id id) astate
|
|
|
|
|
| Sil.Call (_, Const (Cfun callee_pname), _, call_loc, _) ->
|
|
|
|
|
let caller_pname = Procdesc.get_proc_name pdesc in
|
|
|
|
|
let call_site = CallSite.make callee_pname call_loc in
|
|
|
|
|
check_call tenv callee_pname caller_pname call_site astate
|
|
|
|
|
|> merge_callee_map call_site pdesc callee_pname
|
|
|
|
|
| Sil.Load (id, exp, _, _)
|
|
|
|
|
when is_tracking_exp astate exp ->
|
|
|
|
|
Domain.add_tracking_var (Var.of_id id) astate
|
|
|
|
|
| Sil.Store (Exp.Lvar pvar, _, exp, _)
|
|
|
|
|
when is_tracking_exp astate exp ->
|
|
|
|
|
Domain.add_tracking_var (Var.of_pvar pvar) astate
|
|
|
|
|
| Sil.Store (Exp.Lvar pvar, _, _, _) ->
|
|
|
|
|
Domain.remove_tracking_var (Var.of_pvar pvar) astate
|
|
|
|
|
| Sil.Prune (exp, _, _, _)
|
|
|
|
|
when prunes_tracking_var astate exp ->
|
|
|
|
|
Domain.stop_tracking astate
|
|
|
|
|
| Sil.Call (None, _, _, _, _) ->
|
|
|
|
|
failwith "Expecting a return identifier"
|
|
|
|
|
| _ ->
|
|
|
|
|
astate
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
module Analyzer = AbstractInterpreter.Make (ProcCfg.Exceptional) (TransferFunctions)
|
|
|
|
|
|
|
|
|
|
let report_src_snk_path { Callbacks.proc_desc; tenv; summary } sink_map snk_annot src_annot =
|
|
|
|
|
let proc_name = Procdesc.get_proc_name proc_desc in
|
|
|
|
|
let loc = Procdesc.get_loc proc_desc in
|
|
|
|
@ -370,6 +249,9 @@ let report_src_snk_paths proc_data annot_map src_annot_list snk_annot =
|
|
|
|
|
|
|
|
|
|
(* New implementation starts here *)
|
|
|
|
|
|
|
|
|
|
let annotation_of_str annot_str =
|
|
|
|
|
{ Annot.class_name = annot_str; parameters = []; }
|
|
|
|
|
|
|
|
|
|
module AnnotationSpec = struct
|
|
|
|
|
type predicate = Tenv.t -> Typ.Procname.t -> bool
|
|
|
|
|
|
|
|
|
@ -377,6 +259,7 @@ module AnnotationSpec = struct
|
|
|
|
|
source_predicate: predicate;
|
|
|
|
|
sink_predicate: predicate;
|
|
|
|
|
sanitizer_predicate: predicate;
|
|
|
|
|
sink_annotation: Annot.t;
|
|
|
|
|
report: Callbacks.proc_callback_args -> AnnotReachabilityDomain.astate -> unit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -391,8 +274,12 @@ module StandardAnnotationSpec = struct
|
|
|
|
|
let from_annotations src_annots snk_annot = AnnotationSpec.{
|
|
|
|
|
source_predicate = (fun tenv pname ->
|
|
|
|
|
List.exists src_annots ~f:(fun a -> method_overrides_annot a tenv pname));
|
|
|
|
|
sink_predicate = (method_has_annot snk_annot);
|
|
|
|
|
sink_predicate = (fun tenv pname ->
|
|
|
|
|
let has_annot ia = Annotations.ia_ends_with ia snk_annot.Annot.class_name in
|
|
|
|
|
check_attributes has_annot tenv pname
|
|
|
|
|
);
|
|
|
|
|
sanitizer_predicate = default_sanitizer;
|
|
|
|
|
sink_annotation = snk_annot;
|
|
|
|
|
report = (fun proc_data annot_map ->
|
|
|
|
|
report_src_snk_paths proc_data annot_map src_annots snk_annot)
|
|
|
|
|
}
|
|
|
|
@ -405,8 +292,11 @@ module NoAllocationAnnotationSpec = struct
|
|
|
|
|
let spec = AnnotationSpec.{
|
|
|
|
|
source_predicate = (fun tenv pname ->
|
|
|
|
|
method_overrides_annot no_allocation_annot tenv pname);
|
|
|
|
|
sink_predicate = (method_has_annot constructor_annot);
|
|
|
|
|
sanitizer_predicate = method_has_ignore_allocation_annot;
|
|
|
|
|
sink_predicate = (fun tenv pname ->
|
|
|
|
|
is_allocator tenv pname);
|
|
|
|
|
sanitizer_predicate = (fun tenv pname ->
|
|
|
|
|
check_attributes Annotations.ia_is_ignore_allocations tenv pname);
|
|
|
|
|
sink_annotation = constructor_annot;
|
|
|
|
|
report = (fun proc_data annot_map ->
|
|
|
|
|
report_src_snk_paths proc_data annot_map [no_allocation_annot] constructor_annot);
|
|
|
|
|
}
|
|
|
|
@ -439,8 +329,12 @@ module ExpensiveAnnotationSpec = struct
|
|
|
|
|
|
|
|
|
|
let spec = AnnotationSpec.{
|
|
|
|
|
source_predicate = is_expensive;
|
|
|
|
|
sink_predicate = (method_has_annot expensive_annot);
|
|
|
|
|
sink_predicate = (fun tenv pname ->
|
|
|
|
|
let has_annot ia = Annotations.ia_ends_with ia expensive_annot.class_name in
|
|
|
|
|
check_attributes has_annot tenv pname || is_modeled_expensive tenv pname
|
|
|
|
|
);
|
|
|
|
|
sanitizer_predicate = default_sanitizer;
|
|
|
|
|
sink_annotation = expensive_annot;
|
|
|
|
|
report = (fun ({ Callbacks.tenv; proc_desc } as proc_data) astate ->
|
|
|
|
|
let proc_name = Procdesc.get_proc_name proc_desc in
|
|
|
|
|
if is_expensive tenv proc_name then
|
|
|
|
@ -450,6 +344,18 @@ module ExpensiveAnnotationSpec = struct
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
(* parse user-defined specs from .inferconfig *)
|
|
|
|
|
let parse_user_defined_specs = function
|
|
|
|
|
| `List user_specs ->
|
|
|
|
|
let parse_user_spec json =
|
|
|
|
|
let open Yojson.Basic in
|
|
|
|
|
let sources = Util.member "sources" json |> Util.to_list |> List.map ~f:Util.to_string in
|
|
|
|
|
let sinks = Util.member "sink" json |> Util.to_string in
|
|
|
|
|
sources, sinks in
|
|
|
|
|
List.map ~f:parse_user_spec user_specs
|
|
|
|
|
| _ ->
|
|
|
|
|
[]
|
|
|
|
|
|
|
|
|
|
let annot_specs =
|
|
|
|
|
let user_defined_specs =
|
|
|
|
|
let specs = parse_user_defined_specs Config.annotation_reachability_custom_pairs in
|
|
|
|
@ -467,6 +373,89 @@ let annot_specs =
|
|
|
|
|
(annotation_of_str Annotations.for_non_ui_thread)) ::
|
|
|
|
|
user_defined_specs
|
|
|
|
|
|
|
|
|
|
module TransferFunctions (CFG : ProcCfg.S) = struct
|
|
|
|
|
module CFG = CFG
|
|
|
|
|
module Domain = Domain
|
|
|
|
|
type extras = ProcData.no_extras
|
|
|
|
|
|
|
|
|
|
(* This is specific to the @NoAllocation and @PerformanceCritical checker
|
|
|
|
|
and the "unlikely" method is used to guard branches that are expected to run sufficiently
|
|
|
|
|
rarely to not affect the performances *)
|
|
|
|
|
let is_unlikely pname =
|
|
|
|
|
match pname with
|
|
|
|
|
| Typ.Procname.Java java_pname ->
|
|
|
|
|
String.equal (Typ.Procname.java_get_method java_pname) "unlikely"
|
|
|
|
|
| _ -> false
|
|
|
|
|
|
|
|
|
|
let is_tracking_exp astate = function
|
|
|
|
|
| Exp.Var id -> Domain.is_tracked_var (Var.of_id id) astate
|
|
|
|
|
| Exp.Lvar pvar -> Domain.is_tracked_var (Var.of_pvar pvar) astate
|
|
|
|
|
| _ -> false
|
|
|
|
|
|
|
|
|
|
let prunes_tracking_var astate = function
|
|
|
|
|
| Exp.BinOp (Binop.Eq, lhs, rhs)
|
|
|
|
|
when is_tracking_exp astate lhs ->
|
|
|
|
|
Exp.equal rhs Exp.one
|
|
|
|
|
| Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Eq, lhs, rhs), _)
|
|
|
|
|
when is_tracking_exp astate lhs ->
|
|
|
|
|
Exp.equal rhs Exp.zero
|
|
|
|
|
| _ ->
|
|
|
|
|
false
|
|
|
|
|
|
|
|
|
|
let check_call tenv callee_pname caller_pname call_site astate =
|
|
|
|
|
List.fold
|
|
|
|
|
~init:astate
|
|
|
|
|
~f:(fun astate (spec: AnnotationSpec.t) ->
|
|
|
|
|
if spec.sink_predicate tenv callee_pname &&
|
|
|
|
|
not (spec.sanitizer_predicate tenv caller_pname)
|
|
|
|
|
then Domain.add_call_site spec.sink_annotation callee_pname call_site astate
|
|
|
|
|
else astate)
|
|
|
|
|
annot_specs
|
|
|
|
|
|
|
|
|
|
let merge_callee_map call_site pdesc callee_pname astate =
|
|
|
|
|
match Summary.read_summary pdesc callee_pname with
|
|
|
|
|
| None -> astate
|
|
|
|
|
| Some callee_call_map ->
|
|
|
|
|
let add_call_site annot sink calls astate =
|
|
|
|
|
if AnnotReachabilityDomain.CallSites.is_empty calls
|
|
|
|
|
then astate
|
|
|
|
|
else Domain.add_call_site annot sink call_site astate in
|
|
|
|
|
AnnotReachabilityDomain.fold
|
|
|
|
|
(fun annot sink_map astate ->
|
|
|
|
|
AnnotReachabilityDomain.SinkMap.fold
|
|
|
|
|
(add_call_site annot)
|
|
|
|
|
sink_map
|
|
|
|
|
astate)
|
|
|
|
|
callee_call_map
|
|
|
|
|
astate
|
|
|
|
|
|
|
|
|
|
let exec_instr astate { ProcData.pdesc; tenv; } _ = function
|
|
|
|
|
| Sil.Call (Some (id, _), Const (Cfun callee_pname), _, _, _)
|
|
|
|
|
when is_unlikely callee_pname ->
|
|
|
|
|
Domain.add_tracking_var (Var.of_id id) astate
|
|
|
|
|
| Sil.Call (_, Const (Cfun callee_pname), _, call_loc, _) ->
|
|
|
|
|
let caller_pname = Procdesc.get_proc_name pdesc in
|
|
|
|
|
let call_site = CallSite.make callee_pname call_loc in
|
|
|
|
|
check_call tenv callee_pname caller_pname call_site astate
|
|
|
|
|
|> merge_callee_map call_site pdesc callee_pname
|
|
|
|
|
| Sil.Load (id, exp, _, _)
|
|
|
|
|
when is_tracking_exp astate exp ->
|
|
|
|
|
Domain.add_tracking_var (Var.of_id id) astate
|
|
|
|
|
| Sil.Store (Exp.Lvar pvar, _, exp, _)
|
|
|
|
|
when is_tracking_exp astate exp ->
|
|
|
|
|
Domain.add_tracking_var (Var.of_pvar pvar) astate
|
|
|
|
|
| Sil.Store (Exp.Lvar pvar, _, _, _) ->
|
|
|
|
|
Domain.remove_tracking_var (Var.of_pvar pvar) astate
|
|
|
|
|
| Sil.Prune (exp, _, _, _)
|
|
|
|
|
when prunes_tracking_var astate exp ->
|
|
|
|
|
Domain.stop_tracking astate
|
|
|
|
|
| Sil.Call (None, _, _, _, _) ->
|
|
|
|
|
failwith "Expecting a return identifier"
|
|
|
|
|
| _ ->
|
|
|
|
|
astate
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
module Analyzer = AbstractInterpreter.Make (ProcCfg.Exceptional) (TransferFunctions)
|
|
|
|
|
let checker ({ Callbacks.proc_desc; tenv; summary} as callback) : Specs.summary =
|
|
|
|
|
let initial =
|
|
|
|
|
(AnnotReachabilityDomain.empty, Domain.TrackingDomain.NonBottom Domain.TrackingVar.empty) in
|
|
|
|
|