diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index 32cb316d5..47b4ae6f0 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -975,6 +975,12 @@ and cxx_infer_headers = "Include C++ header models during compilation. Infer swaps some C++ headers for its own in order to get a better model of, eg, the standard library. This can sometimes cause compilation failures." +and cxx_scope_guards = + CLOpt.mk_json ~long:"cxx-scope-guards" + ~in_help:CLOpt.([(Analyze, manual_clang)]) + "Specify scope guard classes that can be read only by destructors without being reported as dead stores." + + and cxx = CLOpt.mk_bool ~long:"cxx" ~default:true ~in_help:CLOpt.([(Capture, manual_clang)]) @@ -2347,6 +2353,8 @@ and cxx = !cxx and cxx_infer_headers = !cxx_infer_headers +and cxx_scope_guards = !cxx_scope_guards + and debug_level_analysis = !debug_level_analysis and debug_level_capture = !debug_level_capture diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index 4cd232681..2285d6220 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -356,6 +356,8 @@ val cxx : bool val cxx_infer_headers : bool +val cxx_scope_guards : Yojson.Basic.json + val debug_level_analysis : int val debug_level_capture : int diff --git a/infer/src/checkers/liveness.ml b/infer/src/checkers/liveness.ml index 02a458180..370dc8ce3 100644 --- a/infer/src/checkers/liveness.ml +++ b/infer/src/checkers/liveness.ml @@ -85,6 +85,37 @@ end module CFG = ProcCfg.OneInstrPerNode (ProcCfg.Backward (ProcCfg.Exceptional)) module Analyzer = AbstractInterpreter.Make (CFG) (TransferFunctions) +(* It's fine to have a dead store on a type that uses the "scope guard" pattern. These types + are only read in their destructors, and this is expected/ok. + (e.g., https://github.com/facebook/folly/blob/master/folly/ScopeGuard.h). *) +let matcher_scope_guard = + let of_json init = function + | `List scope_guards -> + List.fold scope_guards ~f:(fun acc json -> Yojson.Basic.Util.to_string json :: acc) ~init + | _ -> + init + in + let default_scope_guards = + [ (* C++ *) + "folly::RWSpinLock::ReadHolder" + ; "folly::RWSpinLock::WriteHolder" + ; "folly::ScopeGuard" + ; "folly::SharedMutex::ReadHolder" + ; "folly::SharedMutex::WriteHolder" + ; "folly::SharedMutexReadPriority::ReadHolder" + ; "folly::SharedMutexReadPriority::WriteHolder" + ; "folly::SharedMutexWritePriority::ReadHolder" + ; "folly::SharedMutexWritePriority::WriteHolder" + ; "folly::SpinLockGuard" + ; "std::lock_guard" + ; "std::scoped_lock" + ; "std::unique_lock" (* Obj-C *) + ; "CKComponentScope" ] + in + of_json default_scope_guards Config.cxx_scope_guards + |> QualifiedCppName.Match.of_fuzzy_qual_names + + let checker {Callbacks.tenv; summary; proc_desc} : Specs.summary = let cfg = CFG.from_pdesc proc_desc in let invariant_map = @@ -101,28 +132,6 @@ let checker {Callbacks.tenv; summary; proc_desc} : Specs.summary = | _ -> false in - let matcher_scope_guard = - QualifiedCppName.Match.of_fuzzy_qual_names - [ (* C++ *) - "faiss::ScopeDeleter" - ; "folly::RWSpinLock::ReadHolder" - ; "folly::RWSpinLock::WriteHolder" - ; "folly::ScopeGuard" - ; "folly::SharedMutex::ReadHolder" - ; "folly::SharedMutex::WriteHolder" - ; "folly::SharedMutexReadPriority::ReadHolder" - ; "folly::SharedMutexReadPriority::WriteHolder" - ; "folly::SharedMutexWritePriority::ReadHolder" - ; "folly::SharedMutexWritePriority::WriteHolder" - ; "folly::SpinLockGuard" - ; "std::lock_guard" - ; "std::scoped_lock" - ; "std::unique_lock" (* Obj-C *) - ; "CKComponentScope" ] - in - (* It's fine to have a dead store on a type that uses the "scope guard" pattern. These types - are only read in their destructors, and this is expected/ok. - (e.g., https://github.com/facebook/folly/blob/master/folly/ScopeGuard.h). *) let rec is_scope_guard = function | {Typ.desc= Tstruct name} -> QualifiedCppName.Match.match_qualifiers matcher_scope_guard (Typ.Name.qual_name name) @@ -169,3 +178,4 @@ let checker {Callbacks.tenv; summary; proc_desc} : Specs.summary = in List.iter (CFG.nodes cfg) ~f:report_on_node ; summary + diff --git a/infer/tests/codetoanalyze/cpp/liveness/.inferconfig b/infer/tests/codetoanalyze/cpp/liveness/.inferconfig new file mode 100644 index 000000000..bf04eac65 --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/liveness/.inferconfig @@ -0,0 +1,5 @@ +{ + "cxx-scope-guards": [ + "infer::ScopeGuard" + ] +} \ No newline at end of file diff --git a/infer/tests/codetoanalyze/cpp/liveness/dead_stores.cpp b/infer/tests/codetoanalyze/cpp/liveness/dead_stores.cpp index b1daa7f51..cd2c3e22b 100644 --- a/infer/tests/codetoanalyze/cpp/liveness/dead_stores.cpp +++ b/infer/tests/codetoanalyze/cpp/liveness/dead_stores.cpp @@ -11,6 +11,10 @@ #include #include +namespace infer { +class ScopeGuard {}; +}; // namespace infer + namespace folly { class ScopeGuard {}; @@ -301,6 +305,8 @@ void read_holder_ok() { folly::SharedMutex::ReadHolder guard; } void write_holder_ok() { folly::SharedMutex::WriteHolder guard; } +void custom_scope_guard_ok() { infer::ScopeGuard guard; } + struct S { ~S() {} }; diff --git a/infer/tests/codetoanalyze/cpp/liveness/issues.exp b/infer/tests/codetoanalyze/cpp/liveness/issues.exp index 735dd1099..c780f20dc 100644 --- a/infer/tests/codetoanalyze/cpp/liveness/issues.exp +++ b/infer/tests/codetoanalyze/cpp/liveness/issues.exp @@ -6,7 +6,7 @@ codetoanalyze/cpp/liveness/dead_stores.cpp, dead_stores::dead_then_live_bad, 1, codetoanalyze/cpp/liveness/dead_stores.cpp, dead_stores::easy_bad, 0, DEAD_STORE, [Write of unused value] codetoanalyze/cpp/liveness/dead_stores.cpp, dead_stores::init_capture_no_call_bad, 1, DEAD_STORE, [Write of unused value] codetoanalyze/cpp/liveness/dead_stores.cpp, dead_stores::init_capture_reassign_bad, 1, DEAD_STORE, [Write of unused value] -codetoanalyze/cpp/liveness/dead_stores.cpp, dead_stores::lambda_bad::lambda_dead_stores.cpp:160:11_operator(), 1, DEAD_STORE, [Write of unused value] +codetoanalyze/cpp/liveness/dead_stores.cpp, dead_stores::lambda_bad::lambda_dead_stores.cpp:164:11_operator(), 1, DEAD_STORE, [Write of unused value] codetoanalyze/cpp/liveness/dead_stores.cpp, dead_stores::plus_plus1_bad, 2, DEAD_STORE, [Write of unused value] codetoanalyze/cpp/liveness/dead_stores.cpp, dead_stores::plus_plus2_bad, 2, DEAD_STORE, [Write of unused value] codetoanalyze/cpp/liveness/dead_stores.cpp, dead_stores::plus_plus3_bad, 2, DEAD_STORE, [Write of unused value]