@ -39,20 +39,20 @@ let string_list_of_json ~option_name ~init = function
module type LivenessConfig = sig
val is_ blacklisted _destructor : Procname . t -> bool
val is_ dangerous _destructor : Procname . t -> bool
end
(* * Use this config to get a reliable liveness pre-analysis that tells you which variables are live
at which program point * )
module PreAnalysisMode : LivenessConfig = struct
(* * do not do any funky stuff *)
let is_ blacklisted _destructor _ proc_name = false
let is_ dangerous _destructor _ proc_name = false
end
(* * Use this config to get a dead store checker that can take some liberties wrt a strict liveness
analysis * )
module CheckerMode : LivenessConfig = struct
let blacklisted _destructor_matcher =
module CheckerMode = struct
let dangerous _destructor_matcher =
QualifiedCppName . Match . of_fuzzy_qual_names
( string_list_of_json ~ option_name : " liveness-dangerous-classes " ~ init : []
Config . liveness_dangerous_classes )
@ -63,29 +63,41 @@ module CheckerMode : LivenessConfig = struct
QualifiedCppName . Match . of_fuzzy_qual_names [ " std::unique_ptr " ; " std::shared_ptr " ]
let is_ blacklisted _class_name class_name =
let is_ dangerous _class_name class_name =
Typ . Name . unqualified_name class_name
| > QualifiedCppName . Match . match_qualifiers blacklisted _destructor_matcher
| > QualifiedCppName . Match . match_qualifiers dangerous _destructor_matcher
let is_wrapper_of_ blacklisted _class_name class_name =
let is_wrapper_of_ dangerous _class_name class_name =
Typ . Name . unqualified_name class_name
| > QualifiedCppName . Match . match_qualifiers standard_wrappers_matcher
&&
match Typ . Name . get_template_spec_info class_name with
| Some ( Template { args = TType { desc = Tstruct name } :: _ ; _ } ) ->
is_ blacklisted _class_name name
is_ dangerous _class_name name
| _ ->
false
let is_ blacklisted_destructor ( callee_p name : Procname . t ) =
match callee _p name with
| ObjC_Cpp cpp_pname when Procname . ObjC_Cpp . is_destructor cpp_pname ->
is_ blacklisted _class_name cpp_pname . class_name
| | is_wrapper_of_ blacklisted _class_name cpp_pname . class_name
let is_ dangerous_proc_name ( proc_ name : Procname . t ) =
match pro c_name with
| ObjC_Cpp cpp_pname ->
is_ dangerous _class_name cpp_pname . class_name
| | is_wrapper_of_ dangerous _class_name cpp_pname . class_name
| _ ->
false
let is_destructor ( proc_name : Procname . t ) =
match proc_name with
| ObjC_Cpp cpp_pname ->
Procname . ObjC_Cpp . is_destructor cpp_pname
| _ ->
false
let is_dangerous_destructor ( proc_name : Procname . t ) =
is_destructor proc_name && is_dangerous_proc_name proc_name
end
(* * compilers 101-style backward transfer functions for liveness analysis. gen a variable when it is
@ -129,8 +141,8 @@ module TransferFunctions (LConfig : LivenessConfig) (CFG : ProcCfg.S) = struct
| Sil . Prune ( exp , _ , _ , _ ) ->
exp_add_live exp astate
| Sil . Call ( ( ret_id , _ ) , Const ( Cfun callee_pname ) , _ , _ , _ )
when LConfig . is_ blacklisted _destructor callee_pname ->
Logging . d_printfln_escaped " Blacklisted destructor %a, ignoring reads@\n " Procname . pp
when LConfig . is_ dangerous _destructor callee_pname ->
Logging . d_printfln_escaped " Dangerous destructor %a, ignoring reads@\n " Procname . pp
callee_pname ;
Domain . remove ( Var . of_id ret_id ) astate
| Sil . Call ( ( ret_id , _ ) , call_exp , actuals , _ , { CallFlags . cf_assign_last_arg } ) ->
@ -166,37 +178,62 @@ let matcher_scope_guard =
| > QualifiedCppName . Match . of_fuzzy_qual_names
module Captur edByRefTransferFunctions ( CFG : ProcCfg . S ) = struct
module Pass edByRefTransferFunctions ( CFG : ProcCfg . S ) = struct
module CFG = CFG
module Domain = VarSet
type analysis_data = unit
let exec_instr astate () _ instr =
List . fold ( Sil . exps_of_instr instr )
~ f : ( fun acc exp ->
Exp . fold_captured exp
~ f : ( fun acc exp ->
match Exp . ignore_cast exp with
| Exp . Lvar pvar ->
(* captured by reference, add *)
Domain . add ( Var . of_pvar pvar ) acc
| _ ->
(* captured by value or init-capture, skip *)
acc )
acc )
~ init : astate
let add_if_lvar expr astate =
match Exp . ignore_cast expr with
| Exp . Lvar pvar ->
(* passed or captured by reference, add *)
Domain . add ( Var . of_pvar pvar ) astate
| _ ->
(* passed or captured by value or init-capture, skip *)
astate
let proc_name_of_expr expr =
match ( expr : Exp . t ) with Const ( Cfun proc_name ) -> Some proc_name | _ -> None
let is_dangerous expr =
(* ignore all captures from "dangerous" classes *)
proc_name_of_expr expr | > Option . exists ~ f : CheckerMode . is_dangerous_proc_name
let exec_instr astate () _ ( instr : Sil . instr ) =
let astate =
match instr with
| Call ( _ ret , f , actuals , _ loc , _ flags ) when not ( is_dangerous f ) ->
let actuals =
if Option . exists ( proc_name_of_expr f ) ~ f : Procname . is_constructor then
(* Skip "this" in constructors, assuming constructors are less likely to have global
side effects that store " this " in the global state . We could also skip " this " in
all ( non - static ) methods but this becomes less clear : constructing an object then
calling methods on it can have side - effects even if the object is used for nothing
else . * )
List . tl actuals | > Option . value ~ default : []
else actuals
in
List . fold actuals ~ init : astate ~ f : ( fun astate ( actual , _ typ ) -> add_if_lvar actual astate )
| _ ->
astate
in
List . fold ( Sil . exps_of_instr instr ) ~ init : astate ~ f : ( fun astate exp ->
Exp . fold_captured exp ~ f : ( fun astate exp -> add_if_lvar exp astate ) astate )
let pp_session_name _ node fmt = F . pp_print_string fmt " captured by ref "
let pp_session_name _ node fmt = F . pp_print_string fmt " passed by reference "
end
module CapturedByRefAnalyzer =
AbstractInterpreter . MakeRPO ( CapturedByRefTransferFunctions ( ProcCfg . Exceptional ) )
module Pass edByRefAnalyzer =
AbstractInterpreter . MakeRPO ( Pass edByRefTransferFunctions ( ProcCfg . Exceptional ) )
let get_captured_by_ref_invariant_map proc_desc =
let get_ pass ed_by_ref_invariant_map proc_desc =
let cfg = ProcCfg . Exceptional . from_pdesc proc_desc in
CapturedByRefAnalyzer . exec_cfg cfg () ~ initial : VarSet . empty
Pass edByRefAnalyzer. exec_cfg cfg () ~ initial : VarSet . empty
module IntLitSet = Caml . Set . Make ( IntLit )
@ -215,7 +252,7 @@ let ignored_constants =
let checker { IntraproceduralAnalysis . proc_desc ; err_log } =
let captured_by_ref_invariant_map = get_captur ed_by_ref_invariant_map proc_desc in
let passed_by_ref_invariant_map = get_pass ed_by_ref_invariant_map proc_desc in
let cfg = CFG . from_pdesc proc_desc in
let invariant_map = CheckerAnalyzer . exec_cfg cfg proc_desc ~ initial : Domain . empty in
(* we don't want to report in harmless cases like int i = 0; if ( ... ) { i = ... } else { i = ... }
@ -250,11 +287,11 @@ let checker {IntraproceduralAnalysis.proc_desc; err_log} =
| > Option . exists ~ f : ( fun local ->
local . ProcAttributes . is_constexpr | | local . ProcAttributes . is_declared_unused )
in
let should_report pvar typ live_vars captur ed_by_ref_vars =
let should_report pvar typ live_vars pass ed_by_ref_vars =
not
( Pvar . is_frontend_tmp pvar | | Pvar . is_return pvar | | Pvar . is_global pvar
| | is_constexpr_or_unused pvar
| | VarSet . mem ( Var . of_pvar pvar ) captur ed_by_ref_vars
| | VarSet . mem ( Var . of_pvar pvar ) pass ed_by_ref_vars
| | Domain . mem ( Var . of_pvar pvar ) live_vars
| | Procdesc . is_captured_pvar proc_desc pvar
| | is_scope_guard typ
@ -268,15 +305,14 @@ let checker {IntraproceduralAnalysis.proc_desc; err_log} =
let ltr = [ Errlog . make_trace_element 0 loc " Write of unused value " [] ] in
Reporting . log_issue proc_desc err_log ~ loc ~ ltr Liveness IssueType . dead_store message
in
let report_dead_store live_vars captur ed_by_ref_vars = function
let report_dead_store live_vars pass ed_by_ref_vars = function
| Sil . Store { e1 = Lvar pvar ; typ ; e2 = rhs_exp ; loc }
when should_report pvar typ live_vars captur ed_by_ref_vars && not ( is_sentinel_exp rhs_exp ) ->
when should_report pvar typ live_vars pass ed_by_ref_vars && not ( is_sentinel_exp rhs_exp ) ->
log_report pvar typ loc
| Sil . Call ( _ , e_fun , ( arg , typ ) :: _ , loc , _ ) -> (
match ( Exp . ignore_cast e_fun , Exp . ignore_cast arg ) with
| Exp . Const ( Cfun ( Procname . ObjC_Cpp _ as pname ) ) , Exp . Lvar pvar
when Procname . is_constructor pname && should_report pvar typ live_vars captured_by_ref_vars
->
when Procname . is_constructor pname && should_report pvar typ live_vars passed_by_ref_vars ->
log_report pvar typ loc
| _ , _ ->
() )
@ -284,11 +320,11 @@ let checker {IntraproceduralAnalysis.proc_desc; err_log} =
()
in
let report_on_node node =
let captur ed_by_ref_vars =
let pass ed_by_ref_vars =
match
Captur edByRefAnalyzer. extract_post
Pass edByRefAnalyzer. extract_post
( ProcCfg . Exceptional . Node . id ( CFG . Node . underlying_node node ) )
captur ed_by_ref_invariant_map
pass ed_by_ref_invariant_map
with
| Some post ->
post
@ -299,7 +335,7 @@ let checker {IntraproceduralAnalysis.proc_desc; err_log} =
Instrs . iter ( CFG . instrs node ) ~ f : ( fun instr ->
match CheckerAnalyzer . extract_pre node_id invariant_map with
| Some live_vars ->
report_dead_store live_vars captur ed_by_ref_vars instr
report_dead_store live_vars pass ed_by_ref_vars instr
| None ->
() )
in