Reviewed By: jeremydubreil Differential Revision: D3154599 fb-gh-sync-id: 95a4772 fbshipit-source-id: 95a4772master
parent
a5e1a6c77e
commit
f8ecbd800a
@ -0,0 +1,332 @@
|
||||
(*
|
||||
* Copyright (c) 2015 - 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.
|
||||
*)
|
||||
|
||||
module F = Format
|
||||
|
||||
let compare_call (pname1, loc1) (pname2, loc2) =
|
||||
let n = Procname.compare pname1 pname2 in
|
||||
if n <> 0
|
||||
then n
|
||||
else Location.compare loc1 loc2
|
||||
|
||||
let pp_call fmt (pname, loc) = F.fprintf fmt "%a at %a" Procname.pp pname Location.pp loc
|
||||
|
||||
module CallSet = PrettyPrintable.MakePPSet(struct
|
||||
type t = Specs.call
|
||||
let compare = compare_call
|
||||
let pp_element = pp_call
|
||||
end)
|
||||
|
||||
module CallSetDomain = AbstractDomain.FiniteSet(CallSet)
|
||||
|
||||
module Domain = struct
|
||||
include AbstractDomain.Pair(CallSetDomain)(CallSetDomain)
|
||||
|
||||
let add_expensive call (expensive_calls, allocations) =
|
||||
CallSetDomain.add call expensive_calls, allocations
|
||||
|
||||
let add_allocation alloc (expensive_calls, allocations) =
|
||||
expensive_calls, CallSetDomain.add alloc allocations
|
||||
end
|
||||
|
||||
let call_summary_of_astate (astate_expensive, astate_allocations) =
|
||||
let expensive_calls = CallSet.elements astate_expensive in
|
||||
let allocations = CallSet.elements astate_allocations in
|
||||
{ Specs.expensive_calls; allocations; }
|
||||
|
||||
module Summary = Summary.Make (struct
|
||||
type summary = Domain.astate
|
||||
|
||||
let update_payload astate payload =
|
||||
let call_summary = call_summary_of_astate astate in
|
||||
{ payload with Specs.calls = Some call_summary }
|
||||
|
||||
let read_from_payload payload =
|
||||
match payload.Specs.calls with
|
||||
| Some call_summary ->
|
||||
CallSet.of_list call_summary.Specs.expensive_calls,
|
||||
CallSet.of_list call_summary.Specs.allocations
|
||||
| None -> Domain.initial
|
||||
end)
|
||||
|
||||
(* Warning name when a performance critical method directly or indirectly
|
||||
calls a method annotatd as expensive *)
|
||||
let calls_expensive_method =
|
||||
"CHECKERS_CALLS_EXPENSIVE_METHOD"
|
||||
|
||||
(* Warning name when a performance critical method directly or indirectly
|
||||
calls a method allocating memory *)
|
||||
let allocates_memory =
|
||||
"CHECKERS_ALLOCATES_MEMORY"
|
||||
|
||||
(* Warning name for the subtyping rule: method not annotated as expensive cannot be overridden
|
||||
by a method annotated as expensive *)
|
||||
let expensive_overrides_unexpensive =
|
||||
"CHECKERS_EXPENSIVE_OVERRIDES_UNANNOTATED"
|
||||
|
||||
let is_modeled_expensive =
|
||||
let matcher =
|
||||
lazy (let config_file = Inferconfig.inferconfig () in
|
||||
Inferconfig.ModeledExpensiveMatcher.load_matcher config_file) in
|
||||
fun tenv proc_name -> match proc_name with
|
||||
| Procname.Java proc_name_java ->
|
||||
not (Builtin.is_registered proc_name) &&
|
||||
let classname =
|
||||
Typename.Java.from_string (Procname.java_get_class_name proc_name_java) in
|
||||
(Lazy.force matcher) (AndroidFramework.is_subclass tenv classname) proc_name
|
||||
| _ ->
|
||||
false
|
||||
|
||||
let check_attributes check tenv pname =
|
||||
let check_class_attributes check tenv = function
|
||||
| Procname.Java java_pname ->
|
||||
begin
|
||||
match Annotations.get_declaring_class_annotations java_pname tenv with
|
||||
| Some annotations -> check annotations
|
||||
| None -> false
|
||||
end
|
||||
| _ -> false in
|
||||
let check_method_attributes check pname =
|
||||
match Specs.proc_resolve_attributes pname with
|
||||
| None -> false
|
||||
| Some attributes ->
|
||||
let annotated_signature = Annotations.get_annotated_signature attributes in
|
||||
let ret_annotation, _ = annotated_signature.Annotations.ret in
|
||||
check ret_annotation in
|
||||
check_class_attributes check tenv pname || check_method_attributes check pname
|
||||
|
||||
let is_expensive tenv pname =
|
||||
check_attributes Annotations.ia_is_expensive tenv pname
|
||||
|
||||
|
||||
let method_is_performance_critical tenv pname =
|
||||
check_attributes Annotations.ia_is_performance_critical tenv pname
|
||||
|
||||
|
||||
let method_is_no_allocation tenv pname =
|
||||
check_attributes Annotations.ia_is_no_allocation tenv pname
|
||||
|
||||
|
||||
let method_overrides is_annotated tenv pname =
|
||||
let overrides () =
|
||||
let found = ref false in
|
||||
PatternMatch.proc_iter_overridden_methods
|
||||
(fun pn -> found := is_annotated tenv pn)
|
||||
tenv pname;
|
||||
!found in
|
||||
is_annotated tenv pname ||
|
||||
overrides ()
|
||||
|
||||
let method_overrides_performance_critical tenv pname =
|
||||
method_overrides method_is_performance_critical tenv pname
|
||||
|
||||
let method_overrides_no_allocation tenv pname =
|
||||
method_overrides method_is_no_allocation tenv pname
|
||||
|
||||
let method_is_expensive tenv pname =
|
||||
is_modeled_expensive tenv pname ||
|
||||
check_attributes Annotations.ia_is_expensive tenv pname
|
||||
|
||||
let lookup_call_summary pname =
|
||||
match Specs.get_summary pname with
|
||||
| None -> None
|
||||
| Some summary -> summary.Specs.payload.Specs.calls
|
||||
|
||||
let lookup_expensive_calls pname =
|
||||
match lookup_call_summary pname with
|
||||
| None -> []
|
||||
| Some { Specs.expensive_calls } -> expensive_calls
|
||||
|
||||
let lookup_allocations pname =
|
||||
match lookup_call_summary pname with
|
||||
| None -> []
|
||||
| Some { Specs.allocations } -> allocations
|
||||
|
||||
let method_calls_expensive tenv pname =
|
||||
let calls_expensive () =
|
||||
match lookup_call_summary pname with
|
||||
| Some { Specs.expensive_calls } ->
|
||||
expensive_calls <> []
|
||||
| None -> false in
|
||||
method_is_expensive tenv pname ||
|
||||
calls_expensive ()
|
||||
|
||||
let is_allocator tenv pname = match pname with
|
||||
| Procname.Java pname_java ->
|
||||
let is_throwable () =
|
||||
let class_name =
|
||||
Typename.Java.from_string (Procname.java_get_class_name pname_java) in
|
||||
AndroidFramework.is_throwable tenv class_name in
|
||||
Procname.is_constructor pname
|
||||
&& not (Builtin.is_registered pname)
|
||||
&& not (is_throwable ())
|
||||
| _ ->
|
||||
false
|
||||
|
||||
let method_allocates tenv pname =
|
||||
let annotated_ignore_allocation =
|
||||
check_attributes Annotations.ia_is_ignore_allocations tenv pname in
|
||||
let allocates () =
|
||||
match lookup_call_summary pname with
|
||||
| Some { Specs.allocations } ->
|
||||
allocations <> []
|
||||
| None -> false in
|
||||
not annotated_ignore_allocation
|
||||
&& (is_allocator tenv pname || allocates ())
|
||||
|
||||
let lookup_location pname =
|
||||
match Specs.get_summary pname with
|
||||
| None -> Location.dummy
|
||||
| Some summary -> summary.Specs.attributes.ProcAttributes.loc
|
||||
|
||||
let string_of_pname =
|
||||
Procname.to_simplified_string ~withclass:true
|
||||
|
||||
let update_trace trace loc =
|
||||
if Location.equal loc Location.dummy then trace
|
||||
else
|
||||
let trace_elem = {
|
||||
Errlog.lt_level = 0;
|
||||
lt_loc = loc;
|
||||
lt_description = "";
|
||||
lt_node_tags = [];
|
||||
} in
|
||||
trace_elem :: trace
|
||||
|
||||
let report_expensive_call_stack pname loc trace stack_str expensive_pname call_loc =
|
||||
let final_trace = IList.rev (update_trace trace call_loc) in
|
||||
let exp_pname_str = string_of_pname expensive_pname in
|
||||
let description =
|
||||
Printf.sprintf
|
||||
"Method `%s` annotated with `@%s` calls `%s%s` where `%s` is annotated with `@%s`"
|
||||
(Procname.to_simplified_string pname)
|
||||
Annotations.performance_critical
|
||||
stack_str
|
||||
exp_pname_str
|
||||
exp_pname_str
|
||||
Annotations.expensive in
|
||||
let exn =
|
||||
Exceptions.Checkers (calls_expensive_method, Localise.verbatim_desc description) in
|
||||
Reporting.log_error pname ~loc: (Some loc) ~ltr: (Some final_trace) exn
|
||||
|
||||
let report_allocation_stack pname loc trace stack_str constructor_pname call_loc =
|
||||
let final_trace = IList.rev (update_trace trace call_loc) in
|
||||
let constr_str = string_of_pname constructor_pname in
|
||||
let description =
|
||||
Printf.sprintf
|
||||
"Method `%s` annotated with `@%s` allocates `%s` via `%s%s`"
|
||||
(Procname.to_simplified_string pname)
|
||||
Annotations.no_allocation
|
||||
constr_str
|
||||
stack_str
|
||||
("new "^constr_str) in
|
||||
let exn =
|
||||
Exceptions.Checkers (allocates_memory, Localise.verbatim_desc description) in
|
||||
Reporting.log_error pname ~loc: (Some loc) ~ltr: (Some final_trace) exn
|
||||
|
||||
let report_call_stack end_of_stack lookup_next_calls report pname pdesc loc calls =
|
||||
let rec loop visited_pnames (trace, stack_str) (callee_pname, callee_loc) =
|
||||
if end_of_stack callee_pname then
|
||||
report pname loc trace stack_str callee_pname callee_loc
|
||||
else
|
||||
let next_calls = lookup_next_calls callee_pname in
|
||||
let callee_pname_str = string_of_pname callee_pname in
|
||||
let new_stack_str = stack_str ^ callee_pname_str ^ " -> " in
|
||||
let new_trace = update_trace trace callee_loc in
|
||||
let unseen_pnames, updated_visited =
|
||||
IList.fold_left
|
||||
(fun (accu, set) (p, loc) ->
|
||||
if Procname.Set.mem p set then (accu, set)
|
||||
else ((p, loc) :: accu, Procname.Set.add p set))
|
||||
([], visited_pnames) next_calls in
|
||||
IList.iter (loop updated_visited (new_trace, new_stack_str)) unseen_pnames in
|
||||
let start_trace = update_trace [] (Cfg.Procdesc.get_loc pdesc) in
|
||||
IList.iter (loop Procname.Set.empty (start_trace, "")) calls
|
||||
|
||||
let report_expensive_calls tenv pname pdesc loc calls =
|
||||
report_call_stack
|
||||
(method_is_expensive tenv) lookup_expensive_calls
|
||||
report_expensive_call_stack pname pdesc loc calls
|
||||
|
||||
let report_allocations pname pdesc loc calls =
|
||||
report_call_stack
|
||||
Procname.is_constructor lookup_allocations
|
||||
report_allocation_stack pname pdesc loc calls
|
||||
|
||||
module TransferFunctions = struct
|
||||
type astate = Domain.astate
|
||||
|
||||
let exec_instr astate { ProcData.pdesc; tenv; } = function
|
||||
| Sil.Call (_, Const (Cfun callee_pname), _, _, _) ->
|
||||
(* populates the summary for pname; needed for [lookup_location],
|
||||
[method_calls_expensive], and [method_allocates] *)
|
||||
ignore(Summary.read_summary pdesc callee_pname);
|
||||
let call_loc = lookup_location callee_pname in
|
||||
let add_expensive_calls astate =
|
||||
if method_calls_expensive tenv callee_pname
|
||||
then Domain.add_expensive (callee_pname, call_loc) astate
|
||||
else astate in
|
||||
let add_allocations astate =
|
||||
if method_allocates tenv callee_pname
|
||||
then Domain.add_allocation (callee_pname, call_loc) astate
|
||||
else astate in
|
||||
add_expensive_calls astate
|
||||
|> add_allocations
|
||||
| _ -> astate
|
||||
end
|
||||
|
||||
module Analyzer =
|
||||
AbstractInterpreter.Make
|
||||
(ProcCfg.Forward)
|
||||
(Scheduler.ReversePostorder)
|
||||
(Domain)
|
||||
(TransferFunctions)
|
||||
|
||||
module Interprocedural = struct
|
||||
include Analyzer.Interprocedural(Summary)
|
||||
|
||||
let report_expensive_calls tenv pname pdesc loc calls =
|
||||
report_call_stack
|
||||
(method_is_expensive tenv) lookup_expensive_calls
|
||||
report_expensive_call_stack pname pdesc loc calls
|
||||
|
||||
let check_and_report ({ Callbacks.proc_desc; proc_name; tenv; } as proc_data) =
|
||||
let loc = Cfg.Procdesc.get_loc proc_desc in
|
||||
let expensive = is_expensive tenv proc_name
|
||||
and performance_critical =
|
||||
method_overrides_performance_critical tenv proc_name
|
||||
and no_allocation =
|
||||
method_overrides_no_allocation tenv proc_name in
|
||||
|
||||
let check_expensive_subtyping_rules overridden_pname =
|
||||
if not (method_is_expensive tenv overridden_pname) then
|
||||
let description =
|
||||
Printf.sprintf
|
||||
"Method `%s` overrides unannotated method `%s` and cannot be annotated with `@%s`"
|
||||
(Procname.to_string proc_name)
|
||||
(Procname.to_string overridden_pname)
|
||||
Annotations.expensive in
|
||||
let exn =
|
||||
Exceptions.Checkers
|
||||
(expensive_overrides_unexpensive, Localise.verbatim_desc description) in
|
||||
Reporting.log_error proc_name ~loc: (Some loc) ~ltr: None exn in
|
||||
|
||||
if expensive then
|
||||
PatternMatch.proc_iter_overridden_methods
|
||||
check_expensive_subtyping_rules tenv proc_name;
|
||||
|
||||
match checker proc_data with
|
||||
| Some astate ->
|
||||
if performance_critical then
|
||||
report_expensive_calls tenv proc_name proc_desc loc (CallSet.elements (fst astate));
|
||||
if no_allocation then
|
||||
report_allocations proc_name proc_desc loc (CallSet.elements (snd astate))
|
||||
| None -> ()
|
||||
|
||||
end
|
Loading…
Reference in new issue