Skip to content

Commit bbd2676

Browse files
Nikos Gorogiannisfacebook-github-bot
authored andcommitted
[starvation] c++/Obj C deadlocks
Reviewed By: da319 Differential Revision: D9042172 fbshipit-source-id: a7052e061
1 parent 5b3bca5 commit bbd2676

File tree

6 files changed

+132
-29
lines changed

6 files changed

+132
-29
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ DIRECT_TESTS += \
6363
cpp_quandary cpp_quandaryBO \
6464
cpp_racerd \
6565
cpp_siof \
66+
cpp_starvation \
6667
cpp_uninit \
6768

6869
ifneq ($(BUCK),no)

infer/src/checkers/registerCheckers.ml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ let all_checkers =
121121
; active= Config.starvation
122122
; callbacks=
123123
[ (Procedure Starvation.analyze_procedure, Language.Java)
124-
; (Cluster Starvation.reporting, Language.Java) ] }
124+
; (Cluster Starvation.reporting, Language.Java)
125+
; (Procedure Starvation.analyze_procedure, Language.Clang)
126+
; (Cluster Starvation.reporting, Language.Clang) ] }
125127
; {name= "purity"; active= Config.purity; callbacks= [(Procedure Purity.checker, Language.Java)]}
126128
; { name= "Class loading analysis"
127129
; active= Config.class_loads

infer/src/concurrency/starvation.ml

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ let pname_pp = MF.wrap_monospaced Typ.Procname.pp
1313

1414
let debug fmt = L.(debug Analysis Verbose fmt)
1515

16-
let is_on_ui_thread pn =
16+
let is_ui_thread_model pn =
1717
ConcurrencyModels.(match get_thread pn with MainThread -> true | _ -> false)
1818

1919

@@ -25,7 +25,7 @@ let is_nonblocking tenv proc_desc =
2525
let is_class_suppressed =
2626
PatternMatch.get_this_type proc_attributes
2727
|> Option.bind ~f:(PatternMatch.type_get_annotation tenv)
28-
|> Option.value_map ~default:false ~f:Annotations.ia_is_nonblocking
28+
|> Option.exists ~f:Annotations.ia_is_nonblocking
2929
in
3030
is_method_suppressed || is_class_suppressed
3131

@@ -39,13 +39,14 @@ module Payload = SummaryPayload.Make (struct
3939
end)
4040

4141
(* using an indentifier for a class object, create an access path representing that lock;
42-
this is for synchronizing on class objects only *)
43-
let lock_of_class class_id =
44-
let ident = Ident.create_normal class_id 0 in
42+
this is for synchronizing on Java class objects only *)
43+
let lock_of_class =
4544
let type_name = Typ.Name.Java.from_string "java.lang.Class" in
4645
let typ = Typ.mk (Typ.Tstruct type_name) in
4746
let typ' = Typ.mk (Typ.Tptr (typ, Typ.Pk_pointer)) in
48-
AccessPath.of_id ident typ'
47+
fun class_id ->
48+
let ident = Ident.create_normal class_id 0 in
49+
AccessPath.of_id ident typ'
4950

5051

5152
module TransferFunctions (CFG : ProcCfg.S) = struct
@@ -57,13 +58,11 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
5758
let exec_instr (astate : Domain.astate) {ProcData.pdesc; tenv; extras} _ (instr : HilInstr.t) =
5859
let open ConcurrencyModels in
5960
let open StarvationModels in
60-
let is_formal base = FormalMap.is_formal base extras in
61-
let get_lock_path actuals =
62-
match actuals with
61+
let get_lock_path = function
6362
| HilExp.AccessExpression access_exp -> (
6463
match AccessExpression.to_access_path access_exp with
6564
| (((Var.ProgramVar pvar, _) as base), _) as path
66-
when is_formal base || Pvar.is_global pvar ->
65+
when FormalMap.is_formal base extras || Pvar.is_global pvar ->
6766
Some (AccessPath.inner_class_normalize path)
6867
| _ ->
6968
(* ignore paths on local or logical variables *)
@@ -78,6 +77,11 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
7877
List.filter_map ~f:get_lock_path locks |> Domain.acquire astate loc
7978
in
8079
let do_unlock locks astate = List.filter_map ~f:get_lock_path locks |> Domain.release astate in
80+
let do_call callee loc =
81+
Payload.read pdesc callee
82+
|> Option.value_map ~default:astate ~f:(Domain.integrate_summary astate callee loc)
83+
in
84+
let is_java = Procdesc.get_proc_name pdesc |> Typ.Procname.is_java in
8185
match instr with
8286
| Call (_, Direct callee, actuals, _, loc) -> (
8387
match get_lock_effect callee actuals with
@@ -91,19 +95,22 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
9195
astate
9296
| NoEffect when is_synchronized_library_call tenv callee ->
9397
(* model a synchronized call without visible internal behaviour *)
94-
do_lock actuals loc astate |> do_unlock actuals
95-
| NoEffect when is_on_ui_thread callee ->
98+
let locks = List.hd actuals |> Option.to_list in
99+
do_lock locks loc astate |> do_unlock locks
100+
| NoEffect when is_java && is_ui_thread_model callee ->
96101
let explanation = F.asprintf "it calls %a" pname_pp callee in
97102
Domain.set_on_ui_thread astate loc explanation
98-
| NoEffect when StarvationModels.is_strict_mode_violation tenv callee actuals ->
103+
| NoEffect when is_java && StarvationModels.is_strict_mode_violation tenv callee actuals ->
99104
Domain.strict_mode_call callee loc astate
100-
| NoEffect -> (
105+
| NoEffect when is_java -> (
101106
match may_block tenv callee actuals with
102107
| Some sev ->
103108
Domain.blocking_call callee sev loc astate
104109
| None ->
105-
Payload.read pdesc callee
106-
|> Option.value_map ~default:astate ~f:(Domain.integrate_summary astate callee loc) ) )
110+
do_call callee loc )
111+
| NoEffect ->
112+
(* in C++/Obj C we only care about deadlocks, not starvation errors *)
113+
do_call callee loc )
107114
| _ ->
108115
astate
109116

@@ -113,16 +120,8 @@ end
113120

114121
module Analyzer = LowerHil.MakeAbstractInterpreter (ProcCfg.Normal) (TransferFunctions)
115122

116-
let die_if_not_java proc_desc =
117-
let is_java =
118-
Procdesc.get_proc_name proc_desc |> Typ.Procname.get_language |> Language.(equal Java)
119-
in
120-
if not is_java then L.(die InternalError "Not supposed to run on non-Java code yet.")
121-
122-
123123
let analyze_procedure {Callbacks.proc_desc; tenv; summary} =
124124
let open StarvationDomain in
125-
die_if_not_java proc_desc ;
126125
let pname = Procdesc.get_proc_name proc_desc in
127126
let formals = FormalMap.make proc_desc in
128127
let proc_data = ProcData.make proc_desc tenv formals in
@@ -295,13 +294,16 @@ let should_report_deadlock_on_current_proc current_elem endpoint_elem =
295294

296295

297296
let should_report pdesc =
297+
Procdesc.get_access pdesc <> PredSymb.Private
298+
&&
298299
match Procdesc.get_proc_name pdesc with
299300
| Typ.Procname.Java java_pname ->
300-
Procdesc.get_access pdesc <> PredSymb.Private
301-
&& (not (Typ.Procname.Java.is_autogen_method java_pname))
301+
(not (Typ.Procname.Java.is_autogen_method java_pname))
302302
&& not (Typ.Procname.Java.is_class_initializer java_pname)
303+
| Typ.Procname.ObjC_Cpp _ ->
304+
true
303305
| _ ->
304-
L.(die InternalError "Not supposed to run on non-Java code.")
306+
false
305307

306308

307309
let fold_reportable_summaries (tenv, current_pdesc) clazz ~init ~f =
@@ -474,7 +476,6 @@ let report_starvation env {StarvationDomain.events; ui} report_map' =
474476

475477
let reporting {Callbacks.procedures; source_file} =
476478
let report_procedure ((tenv, proc_desc) as env) =
477-
die_if_not_java proc_desc ;
478479
if should_report proc_desc then
479480
Payload.read proc_desc (Procdesc.get_proc_name proc_desc)
480481
|> Option.iter ~f:(fun summary ->
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright (c) 2018-present, Facebook, Inc.
2+
#
3+
# This source code is licensed under the MIT license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
TESTS_DIR = ../../..
7+
8+
ANALYZER = checkers
9+
# see explanations in cpp/errors/Makefile for the custom isystem
10+
CLANG_OPTIONS = -x c++ -std=c++11 -nostdinc++ -isystem$(ROOT_DIR) -isystem$(CLANG_INCLUDES)/c++/v1/ -c
11+
12+
INFER_OPTIONS = --starvation-only --debug-exceptions --project-root $(TESTS_DIR)
13+
14+
INFERPRINT_OPTIONS = --issues-tests
15+
16+
SOURCES = $(wildcard *.cpp)
17+
18+
include $(TESTS_DIR)/clang.make
19+
20+
infer-out/report.json: $(MAKEFILE_LIST)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2018-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include <mutex>
9+
10+
namespace basics {
11+
12+
class Basic {
13+
public:
14+
Basic() {}
15+
16+
void thread1() {
17+
mutex_1.lock();
18+
mutex_2.lock();
19+
20+
mutex_2.unlock();
21+
mutex_1.unlock();
22+
}
23+
24+
void thread2() {
25+
mutex_2.lock();
26+
mutex_1.lock();
27+
28+
mutex_1.unlock();
29+
mutex_2.unlock();
30+
}
31+
32+
private:
33+
std::mutex mutex_1;
34+
std::mutex mutex_2;
35+
};
36+
37+
class WithGuard {
38+
public:
39+
WithGuard() {}
40+
41+
void thread1() {
42+
std::lock_guard<std::mutex> lock1(mutex_1);
43+
std::lock_guard<std::mutex> lock2(mutex_2);
44+
}
45+
46+
void thread2() {
47+
std::lock_guard<std::mutex> lock2(mutex_2);
48+
std::lock_guard<std::mutex> lock1(mutex_1);
49+
}
50+
51+
private:
52+
std::mutex mutex_1;
53+
std::mutex mutex_2;
54+
};
55+
56+
class StdLock {
57+
public:
58+
StdLock() {}
59+
60+
// no reports, std::lock magically avoids deadlocks
61+
void thread1() {
62+
std::lock<std::mutex>(mutex_1, mutex_2);
63+
mutex_1.unlock();
64+
mutex_2.unlock();
65+
}
66+
67+
void thread2() {
68+
std::lock<std::mutex>(mutex_2, mutex_1);
69+
mutex_2.unlock();
70+
mutex_1.unlock();
71+
}
72+
73+
private:
74+
std::mutex mutex_1;
75+
std::mutex mutex_2;
76+
};
77+
} // namespace basics
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
codetoanalyze/cpp/starvation/basics.cpp, basics::Basic_thread1, 17, DEADLOCK, no_bucket, ERROR, [[Trace 1] `basics::Basic_thread1`,locks `this.mutex_1` in class `basics::Basic*`,locks `this.mutex_2` in class `basics::Basic*`,[Trace 2] `basics::Basic_thread2`,locks `this.mutex_2` in class `basics::Basic*`,locks `this.mutex_1` in class `basics::Basic*`]
2+
codetoanalyze/cpp/starvation/basics.cpp, basics::WithGuard_thread1, 42, DEADLOCK, no_bucket, ERROR, [[Trace 1] `basics::WithGuard_thread1`,locks `this.mutex_1` in class `basics::WithGuard*`,locks `this.mutex_2` in class `basics::WithGuard*`,[Trace 2] `basics::WithGuard_thread2`,locks `this.mutex_2` in class `basics::WithGuard*`,locks `this.mutex_1` in class `basics::WithGuard*`]

0 commit comments

Comments
 (0)