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.
331 lines
10 KiB
331 lines
10 KiB
(*
|
|
* 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 L = Logging
|
|
|
|
|
|
let performance_critical_implies_no_allocation = true
|
|
|
|
|
|
(* 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 ->
|
|
not (SymExec.function_is_builtin proc_name) &&
|
|
let classname =
|
|
Typename.Java.from_string (Procname.java_get_class proc_name) in
|
|
(Lazy.force matcher) (AndroidFramework.is_subclass tenv classname) proc_name
|
|
|
|
|
|
let check_attributes check attributes =
|
|
let annotated_signature = Annotations.get_annotated_signature attributes in
|
|
let ret_annotation, _ = annotated_signature.Annotations.ret in
|
|
check ret_annotation
|
|
|
|
|
|
let is_expensive attributes =
|
|
check_attributes Annotations.ia_is_expensive attributes
|
|
|
|
|
|
let check_method check pname =
|
|
match Specs.proc_resolve_attributes pname with
|
|
| None -> false
|
|
| Some attributes ->
|
|
check_attributes check attributes
|
|
|
|
|
|
let method_is_performance_critical pname =
|
|
check_method Annotations.ia_is_performance_critical pname
|
|
|
|
|
|
let method_is_no_allcation pname =
|
|
(performance_critical_implies_no_allocation
|
|
&& method_is_performance_critical pname)
|
|
|| check_method Annotations.ia_is_no_allocation 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 pn)
|
|
tenv pname;
|
|
!found in
|
|
is_annotated 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_allcation tenv pname
|
|
|
|
|
|
let method_is_expensive tenv pname =
|
|
is_modeled_expensive tenv pname
|
|
|| check_method Annotations.ia_is_expensive 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 =
|
|
let is_throwable () =
|
|
let class_name =
|
|
Typename.Java.from_string (Procname.java_get_class pname) in
|
|
AndroidFramework.is_throwable tenv class_name in
|
|
Procname.is_constructor pname
|
|
&& not (SymExec.function_is_builtin pname)
|
|
&& not (is_throwable ())
|
|
|
|
|
|
let method_allocates tenv pname =
|
|
let annotated_ignore_allocation =
|
|
check_method Annotations.ia_is_ignore_allocations 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 collect_calls tenv caller_pdesc checked_pnames call_summary (pname, _) =
|
|
if Procname.Set.mem pname !checked_pnames then call_summary
|
|
else
|
|
begin
|
|
Ondemand.analyze_proc_name ~propagate_exceptions:true caller_pdesc pname;
|
|
checked_pnames := Procname.Set.add pname !checked_pnames;
|
|
let call_loc = lookup_location pname in
|
|
let updated_expensive_calls =
|
|
if method_calls_expensive tenv pname then
|
|
(pname, call_loc) :: call_summary.Specs.expensive_calls
|
|
else
|
|
call_summary.Specs.expensive_calls in
|
|
let updated_allocations =
|
|
if method_allocates tenv pname then
|
|
(pname, call_loc) :: call_summary.Specs.allocations
|
|
else
|
|
call_summary.Specs.allocations in
|
|
{ Specs.expensive_calls = updated_expensive_calls;
|
|
Specs.allocations = updated_allocations }
|
|
end
|
|
|
|
|
|
let update_summary call_summary pname =
|
|
match Specs.get_summary pname with
|
|
| None -> ()
|
|
| Some summary ->
|
|
let updated_summary =
|
|
{ summary with
|
|
Specs.payload =
|
|
{ summary.Specs.payload with
|
|
Specs.calls = Some call_summary }
|
|
} in
|
|
Specs.add_summary pname updated_summary
|
|
|
|
|
|
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
|
|
|
|
|
|
let check_one_procedure tenv pname pdesc =
|
|
|
|
let loc = Cfg.Procdesc.get_loc pdesc in
|
|
let attributes = Cfg.Procdesc.get_attributes pdesc in
|
|
let expensive = is_expensive attributes
|
|
and performance_critical =
|
|
method_overrides_performance_critical tenv pname
|
|
and no_allocation =
|
|
method_overrides_no_allocation tenv pname 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 pname)
|
|
(Procname.to_string overridden_pname)
|
|
Annotations.expensive in
|
|
let exn =
|
|
Exceptions.Checkers
|
|
(expensive_overrides_unexpensive, Localise.verbatim_desc description) in
|
|
Reporting.log_error pname ~loc: (Some loc) ~ltr: None exn in
|
|
|
|
if expensive then
|
|
PatternMatch.proc_iter_overridden_methods
|
|
check_expensive_subtyping_rules tenv pname;
|
|
|
|
let call_summary =
|
|
let checked_pnames = ref Procname.Set.empty in
|
|
let empty_summary =
|
|
{ Specs.expensive_calls = [];
|
|
allocations = [] } in
|
|
Cfg.Procdesc.fold_calls
|
|
(collect_calls tenv pdesc checked_pnames) empty_summary pdesc in
|
|
|
|
update_summary call_summary pname;
|
|
|
|
if performance_critical then
|
|
report_expensive_calls tenv pname pdesc loc call_summary.Specs.expensive_calls;
|
|
if no_allocation then
|
|
report_allocations pname pdesc loc call_summary.Specs.allocations
|
|
|
|
|
|
let callback_performance_checker
|
|
{ Callbacks.get_proc_desc; proc_desc; proc_name; tenv } =
|
|
let callbacks =
|
|
let analyze_ondemand pdesc =
|
|
check_one_procedure tenv (Cfg.Procdesc.get_proc_name pdesc) pdesc in
|
|
{
|
|
Ondemand.analyze_ondemand;
|
|
get_proc_desc;
|
|
} in
|
|
if Ondemand.procedure_should_be_analyzed proc_name
|
|
then
|
|
begin
|
|
Ondemand.set_callbacks callbacks;
|
|
check_one_procedure tenv proc_name proc_desc;
|
|
Ondemand.unset_callbacks ()
|
|
end
|
|
|
|
(*
|
|
let is_performance_critical attributes =
|
|
check_attributes Annotations.ia_is_performance_critical attributes
|
|
*)
|