[ConfigImpact] Use cost analysis to determine cheapness

Summary:
Reporting all ungated (un configed?) function calls causes many FPs. Instead, we rely on complexity analysis to determine whether a function is cheap/expensive: if the callee's complexity is not symbolic (e.g. constant), we consider it as cheap and don't keep track of it.

Note that we don't take the instantiated/modeled cost into account yet. So, if we have  `foo(int n)` with complexity `O(n)`, and call it as `foo(3)`, we would still keep track of it. Similarly, if `foo` is a modeled function with constant time complexity, we would have no summary for it hence would keep track of it.

These will be improved later.

Reviewed By: skcho

Differential Revision: D27430485

fbshipit-source-id: d5f66320d
master
Ezgi Çiçek 4 years ago committed by Facebook GitHub Bot
parent a89d88063d
commit a6ab4d38cf

@ -194,7 +194,8 @@ let all_checkers =
; { checker= ConfigImpactAnalysis ; { checker= ConfigImpactAnalysis
; callbacks= ; callbacks=
(let checker = (let checker =
interprocedural Payloads.Fields.config_impact_analysis ConfigImpactAnalysis.checker interprocedural2 Payloads.Fields.config_impact_analysis Payloads.Fields.cost
ConfigImpactAnalysis.checker
in in
[(checker, Clang); (checker, Java)] ) } ] [(checker, Clang); (checker, Java)] ) } ]

@ -156,7 +156,7 @@ let config_unsafe checker =
"[EXPERIMENTAL] Collects function that are called without config checks." "[EXPERIMENTAL] Collects function that are called without config checks."
; cli_flags= Some {deprecated= []; show_in_help= true} ; cli_flags= Some {deprecated= []; show_in_help= true}
; enabled_by_default= false ; enabled_by_default= false
; activates= [] } ; activates= [Cost] }
| Cost -> | Cost ->
{ id= "cost" { id= "cost"
; kind= ; kind=

@ -250,7 +250,7 @@ module Dom = struct
; config_fields= Fields.widen ~prev:prev.config_fields ~next:next.config_fields ~num_iters } ; config_fields= Fields.widen ~prev:prev.config_fields ~next:next.config_fields ~num_iters }
let to_summary has_call_stmt {unchecked_callees; unchecked_callees_cond; config_fields} = let to_summary ~has_call_stmt {unchecked_callees; unchecked_callees_cond; config_fields} =
{Summary.unchecked_callees; unchecked_callees_cond; has_call_stmt; config_fields} {Summary.unchecked_callees; unchecked_callees_cond; has_call_stmt; config_fields}
@ -327,40 +327,51 @@ module Dom = struct
let call analyze_dependency callee location let call analyze_dependency callee location
({config_checks; field_checks; unchecked_callees; unchecked_callees_cond} as astate) = ({config_checks; field_checks; unchecked_callees; unchecked_callees_cond} as astate) =
if ConfigChecks.is_top config_checks then if ConfigChecks.is_top config_checks then
let new_unchecked_callees, new_unchecked_callees_cond = let callee_summary = analyze_dependency callee in
match analyze_dependency callee with if
| Some Option.exists callee_summary ~f:(fun (_, (_, (cost_summary : CostDomain.summary option))) ->
( _ Option.exists cost_summary ~f:(fun (cost_summary : CostDomain.summary) ->
, { Summary.unchecked_callees= callee_summary let callee_cost = CostDomain.get_operation_cost cost_summary.post in
; unchecked_callees_cond= callee_summary_cond not (CostDomain.BasicCost.is_symbolic callee_cost.cost) ) )
; has_call_stmt } ) then (* If callee is cheap by heuristics, ignore it. *)
when has_call_stmt -> astate
(* If callee's summary is not leaf, use it. *)
( UncheckedCallees.replace_location_by_call location callee_summary
, UncheckedCalleesCond.replace_location_by_call location callee_summary_cond )
| _ ->
(* Otherwise, add callee's name. *)
( UncheckedCallees.singleton {callee; location; call_type= Direct}
, UncheckedCalleesCond.empty )
in
if FieldChecks.is_top field_checks then
{ astate with
unchecked_callees= UncheckedCallees.join unchecked_callees new_unchecked_callees
; unchecked_callees_cond=
UncheckedCalleesCond.join unchecked_callees_cond new_unchecked_callees_cond }
else else
let fields_to_add = FieldChecks.get_fields field_checks in let new_unchecked_callees, new_unchecked_callees_cond =
let unchecked_callees_cond = match callee_summary with
UncheckedCalleesCond.weak_update fields_to_add new_unchecked_callees | Some
unchecked_callees_cond ( _
in , ( Some
let unchecked_callees_cond = { Summary.unchecked_callees= callee_summary
UncheckedCalleesCond.fold ; unchecked_callees_cond= callee_summary_cond
(fun fields callees acc -> ; has_call_stmt }
UncheckedCalleesCond.weak_update (Fields.union fields fields_to_add) callees acc ) , _callee_cost_summary ) )
new_unchecked_callees_cond unchecked_callees_cond when has_call_stmt ->
(* If callee's summary is not leaf, use it. *)
( UncheckedCallees.replace_location_by_call location callee_summary
, UncheckedCalleesCond.replace_location_by_call location callee_summary_cond )
| _ ->
(* Otherwise, add callee's name. *)
( UncheckedCallees.singleton {callee; location; call_type= Direct}
, UncheckedCalleesCond.empty )
in in
{astate with unchecked_callees_cond} if FieldChecks.is_top field_checks then
{ astate with
unchecked_callees= UncheckedCallees.join unchecked_callees new_unchecked_callees
; unchecked_callees_cond=
UncheckedCalleesCond.join unchecked_callees_cond new_unchecked_callees_cond }
else
let fields_to_add = FieldChecks.get_fields field_checks in
let unchecked_callees_cond =
UncheckedCalleesCond.weak_update fields_to_add new_unchecked_callees
unchecked_callees_cond
in
let unchecked_callees_cond =
UncheckedCalleesCond.fold
(fun fields callees acc ->
UncheckedCalleesCond.weak_update (Fields.union fields fields_to_add) callees acc )
new_unchecked_callees_cond unchecked_callees_cond
in
{astate with unchecked_callees_cond}
else astate else astate
end end
@ -368,7 +379,7 @@ module TransferFunctions = struct
module CFG = ProcCfg.Normal module CFG = ProcCfg.Normal
module Domain = Dom module Domain = Dom
type analysis_data = Summary.t InterproceduralAnalysis.t type analysis_data = (Summary.t option * CostDomain.summary option) InterproceduralAnalysis.t
let is_java_boolean_value_method pname = let is_java_boolean_value_method pname =
Procname.get_class_name pname |> Option.exists ~f:(String.equal "java.lang.Boolean") Procname.get_class_name pname |> Option.exists ~f:(String.equal "java.lang.Boolean")
@ -460,4 +471,4 @@ let has_call_stmt proc_desc =
let checker ({InterproceduralAnalysis.proc_desc} as analysis_data) = let checker ({InterproceduralAnalysis.proc_desc} as analysis_data) =
Option.map (Analyzer.compute_post analysis_data ~initial:Dom.init proc_desc) ~f:(fun astate -> Option.map (Analyzer.compute_post analysis_data ~initial:Dom.init proc_desc) ~f:(fun astate ->
let has_call_stmt = has_call_stmt proc_desc in let has_call_stmt = has_call_stmt proc_desc in
Dom.to_summary has_call_stmt astate ) Dom.to_summary ~has_call_stmt astate )

@ -39,4 +39,5 @@ module Summary : sig
val instantiate_unchecked_callees_cond : all_config_fields:Fields.t -> t -> t val instantiate_unchecked_callees_cond : all_config_fields:Fields.t -> t -> t
end end
val checker : Summary.t InterproceduralAnalysis.t -> Summary.t option val checker :
(Summary.t option * CostDomain.summary option) InterproceduralAnalysis.t -> Summary.t option
Loading…
Cancel
Save