diff --git a/infer/src/concurrency/MethodMatcher.ml b/infer/src/concurrency/MethodMatcher.ml index 971ce07d2..b86b16a08 100644 --- a/infer/src/concurrency/MethodMatcher.ml +++ b/infer/src/concurrency/MethodMatcher.ml @@ -8,6 +8,19 @@ open! IStd module L = Logging +let template_arg = Str.regexp "<[^<>]*>" + +let rec strip_template_args str = + if + (not (String.contains str '<')) + || String.equal str Typ.Procname.Java.constructor_method_name + || String.equal str Typ.Procname.Java.class_initializer_method_name + then str + else + let result = Str.global_replace template_arg "" str in + if String.equal result str then str else strip_template_args result + + (** [call_matches C methods] builds a method matcher for calls [C.foo] where [foo] is in [methods]. Named arguments change behaviour: - [search_superclasses=true] will match calls [S.foo] where [S] is a superclass of [C]. @@ -15,25 +28,30 @@ module L = Logging - [actuals_pred] is a predicate that runs on the expressions fed as arguments to the call, and which must return [true] for the matcher to return [true]. *) let call_matches ~search_superclasses ~method_prefix ~actuals_pred clazz methods = + let clazz = strip_template_args clazz in + let methods = List.map methods ~f:strip_template_args in let method_matcher = if method_prefix then fun current_method target_method -> - String.is_prefix current_method ~prefix:target_method - else fun current_method target_method -> String.equal current_method target_method + String.is_prefix ~prefix:target_method current_method + else fun current_method target_method -> String.equal target_method current_method in let class_matcher = if search_superclasses then let target = "class " ^ clazz in - let is_target tname _tstruct = Typ.Name.to_string tname |> String.equal target in + let is_target tname _tstruct = + Typ.Name.to_string tname |> strip_template_args |> String.equal target + in fun tenv pname -> Typ.Procname.get_class_type_name pname |> Option.exists ~f:(PatternMatch.supertype_exists tenv is_target) else fun _tenv pname -> - Typ.Procname.get_class_name pname |> Option.exists ~f:(String.equal clazz) + Typ.Procname.get_class_name pname |> Option.map ~f:strip_template_args + |> Option.exists ~f:(String.equal clazz) in (fun tenv pn actuals -> actuals_pred actuals && - let mthd = Typ.Procname.get_method pn in + let mthd = Typ.Procname.get_method pn |> strip_template_args in List.exists methods ~f:(method_matcher mthd) && class_matcher tenv pn ) |> Staged.stage diff --git a/infer/src/concurrency/MethodMatcher.mli b/infer/src/concurrency/MethodMatcher.mli index ae9bcec1b..0b4a41579 100644 --- a/infer/src/concurrency/MethodMatcher.mli +++ b/infer/src/concurrency/MethodMatcher.mli @@ -7,7 +7,8 @@ open! IStd -(** pattern matcher for Java methods *) +(** pattern matcher for Java/C++ methods + NB matching is modulo template arguments in C++ classes and functions *) type t = Tenv.t -> Typ.Procname.t -> HilExp.t list -> bool type record = diff --git a/infer/tests/codetoanalyze/cpp/starvation/.inferconfig b/infer/tests/codetoanalyze/cpp/starvation/.inferconfig index ea4a770a6..8df1a5fde 100644 --- a/infer/tests/codetoanalyze/cpp/starvation/.inferconfig +++ b/infer/tests/codetoanalyze/cpp/starvation/.inferconfig @@ -1,4 +1,7 @@ { "force-delete-results-dir": true, - "starvation-skip-analysis" : [ { "classname": "skipped::Skip", "methods": ["skipped_ok"] } ] + "starvation-skip-analysis" : [ + { "classname": "skipped::Skip", "methods": ["skipped_ok"] }, + { "classname": "skipped::SkipTemplate", "methods": ["skipped_ok"] } + ] } diff --git a/infer/tests/codetoanalyze/cpp/starvation/issues.exp b/infer/tests/codetoanalyze/cpp/starvation/issues.exp index b1e454f38..f0fb287e8 100644 --- a/infer/tests/codetoanalyze/cpp/starvation/issues.exp +++ b/infer/tests/codetoanalyze/cpp/starvation/issues.exp @@ -4,4 +4,6 @@ codetoanalyze/cpp/starvation/basics.cpp, basics::SelfDeadlock_complicated_bad, 1 codetoanalyze/cpp/starvation/basics.cpp, basics::SelfDeadlock_interproc1_bad, 114, DEADLOCK, no_bucket, ERROR, [In method `basics::SelfDeadlock_interproc1_bad`, locks `this.mutex_` in `class basics::SelfDeadlock`,Method call: `basics::SelfDeadlock_interproc2_bad`, locks `this.mutex_` in `class basics::SelfDeadlock`] codetoanalyze/cpp/starvation/basics.cpp, basics::SelfDeadlock_thread_bad, 105, DEADLOCK, no_bucket, ERROR, [In method `basics::SelfDeadlock_thread_bad`, locks `this.mutex_` in `class basics::SelfDeadlock`, locks `this.mutex_` in `class basics::SelfDeadlock`] codetoanalyze/cpp/starvation/basics.cpp, basics::WithGuard_thread1_bad, 44, DEADLOCK, no_bucket, ERROR, [[Trace 1] `basics::WithGuard_thread1_bad`, locks `this.mutex_1` in `class basics::WithGuard`, locks `this.mutex_2` in `class basics::WithGuard`,[Trace 2] `basics::WithGuard_thread2_bad`, locks `this.mutex_2` in `class basics::WithGuard`, locks `this.mutex_1` in `class basics::WithGuard`] +codetoanalyze/cpp/starvation/skip.cpp, skipped::SkipTemplate_not_skipped_bad, 44, DEADLOCK, no_bucket, ERROR, [In method `skipped::SkipTemplate_not_skipped_bad`,Method call: `skipped::SkipTemplate_private_deadlock`, locks `this.mutex_` in `class skipped::SkipTemplate`, locks `this.mutex_` in `class skipped::SkipTemplate`] codetoanalyze/cpp/starvation/skip.cpp, skipped::Skip_not_skipped_bad, 19, DEADLOCK, no_bucket, ERROR, [In method `skipped::Skip_not_skipped_bad`,Method call: `skipped::Skip_private_deadlock`, locks `this.mutex_` in `class skipped::Skip`, locks `this.mutex_` in `class skipped::Skip`] +codetoanalyze/cpp/starvation/skip.cpp, skipped::UseTemplate_foo, 53, DEADLOCK, no_bucket, ERROR, [In method `skipped::UseTemplate_foo`,Method call: `skipped::SkipTemplate_not_skipped_bad`,Method call: `skipped::SkipTemplate_private_deadlock`, locks `this.mutex_` in `class skipped::SkipTemplate`, locks `this.mutex_` in `class skipped::SkipTemplate`] diff --git a/infer/tests/codetoanalyze/cpp/starvation/skip.cpp b/infer/tests/codetoanalyze/cpp/starvation/skip.cpp index 414fe1826..d0ae6d084 100644 --- a/infer/tests/codetoanalyze/cpp/starvation/skip.cpp +++ b/infer/tests/codetoanalyze/cpp/starvation/skip.cpp @@ -7,7 +7,7 @@ #include -// the deadlock here is masked by the starvation-skip-analysis option in +// the deadlocks here are masked by the starvation-skip-analysis option in // .inferconfig namespace skipped { class Skip { @@ -27,4 +27,31 @@ class Skip { } }; +template +class SkipTemplate { + private: + T* a_; + std::mutex mutex_; + + void private_deadlock() { + std::lock_guard l(mutex_); + { std::lock_guard l(mutex_); } + } + + public: + void skipped_ok() { private_deadlock(); } + + void not_skipped_bad() { private_deadlock(); } +}; + +class UseTemplate { + public: + void foo() { + SkipTemplate x; + + x.skipped_ok(); + x.not_skipped_bad(); + } +}; + } // namespace skipped