Skip to content

Commit 7872bab

Browse files
authored
Merge pull request #1865 from dabund24/master
Improve MHP precision using ancestor locksets
2 parents fa6d3b4 + 524d91b commit 7872bab

24 files changed

Lines changed: 783 additions & 10 deletions

src/analyses/creationLockset.ml

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
(** creation lockset analysis [creationLockset]
2+
constructs edges on the graph over all threads, where the edges are labelled with must-locksets:
3+
(t_1) ---L--> (t_0) is represented by global t_1 t_0 = L and means that t_1 is protected by all members of L from t_0
4+
5+
@see https://github.com/goblint/analyzer/pull/1865
6+
*)
7+
8+
open Analyses
9+
module TID = ThreadIdDomain.Thread
10+
module TIDs = ConcDomain.ThreadSet
11+
module Lock = LockDomain.MustLock
12+
module Lockset = LockDomain.MustLockset
13+
14+
module Spec = struct
15+
include IdentityUnitContextsSpec
16+
module D = Lattice.Unit
17+
18+
module V = struct
19+
include TID
20+
include StdV
21+
end
22+
23+
module G = MapDomain.MapBot (TID) (Lockset)
24+
25+
let name () = "creationLockset"
26+
let startstate _ = D.bot ()
27+
let exitstate _ = D.bot ()
28+
29+
(** register a global contribution: global.[child_tid] \supseteq [to_contribute]
30+
@param man man at program point
31+
@param to_contribute new edges from [child_tid] to ego thread to register
32+
@param child_tid
33+
*)
34+
let contribute_locks man to_contribute child_tid = man.sideg child_tid to_contribute
35+
36+
(** reflexive-transitive closure of child relation applied to [tid]
37+
and filtered to only include threads, where [tid] is a must-ancestor
38+
@param man any man
39+
@param tid
40+
*)
41+
let must_ancestor_descendants_closure man tid =
42+
let descendants = man.ask @@ Queries.DescendantThreads tid in
43+
let must_ancestors_descendants = TIDs.filter (TID.must_be_ancestor tid) descendants in
44+
TIDs.add tid must_ancestors_descendants
45+
46+
let threadspawn man ~multiple lval f args fman =
47+
let tid_lifted = man.ask Queries.CurrentThreadId in
48+
let child_tid_lifted = fman.ask Queries.CurrentThreadId in
49+
match tid_lifted, child_tid_lifted with
50+
| `Lifted tid, `Lifted child_tid when TID.must_be_ancestor tid child_tid ->
51+
let must_ancestor_descendants = must_ancestor_descendants_closure fman child_tid in
52+
let lockset = man.ask Queries.MustLockset in
53+
let to_contribute = G.singleton tid lockset in
54+
TIDs.iter (contribute_locks man to_contribute) must_ancestor_descendants
55+
| _ -> ()
56+
57+
(** compute all descendant threads that may run along with the ego thread at a program point.
58+
for all of them, tid must be an ancestor
59+
@param man man of ego thread at the program point
60+
@param tid ego thread id
61+
*)
62+
let get_must_ancestor_running_descendants man tid =
63+
let may_created_tids = man.ask Queries.CreatedThreads in
64+
let may_must_ancestor_created_tids =
65+
TIDs.filter (TID.must_be_ancestor tid) may_created_tids
66+
in
67+
let may_transitively_created_tids =
68+
TIDs.fold
69+
(fun child_tid acc -> TIDs.union acc (must_ancestor_descendants_closure man child_tid))
70+
may_must_ancestor_created_tids
71+
(TIDs.empty ())
72+
in
73+
let must_joined_tids = man.ask Queries.MustJoinedThreads in
74+
TIDs.diff may_transitively_created_tids must_joined_tids
75+
76+
(** handle unlock of mutex [lock] *)
77+
let unlock man tid possibly_running_tids lock =
78+
let shrink_locksets des_tid =
79+
let old_creation_lockset = G.find tid (man.global des_tid) in
80+
(* Bot - {something} = Bot. This is exactly what we want in this case! *)
81+
let updated_creation_lockset = Lockset.remove lock old_creation_lockset in
82+
let to_contribute = G.singleton tid updated_creation_lockset in
83+
man.sideg des_tid to_contribute
84+
in
85+
TIDs.iter shrink_locksets possibly_running_tids
86+
87+
(** handle unlock of an unknown mutex. Assumes that any mutex could have been unlocked *)
88+
let unknown_unlock man tid possibly_running_tids =
89+
let evaporate_locksets des_tid =
90+
let to_contribute = G.singleton tid (Lockset.empty ()) in
91+
man.sideg des_tid to_contribute
92+
in
93+
TIDs.iter evaporate_locksets possibly_running_tids
94+
95+
let event man e _ =
96+
match e with
97+
| Events.Unlock addr ->
98+
let tid_lifted = man.ask Queries.CurrentThreadId in
99+
(match tid_lifted with
100+
| `Lifted tid ->
101+
let possibly_running_tids = get_must_ancestor_running_descendants man tid in
102+
let lock_opt = LockDomain.MustLock.of_addr addr in
103+
(match lock_opt with
104+
| Some lock -> unlock man tid possibly_running_tids lock
105+
| None -> unknown_unlock man tid possibly_running_tids)
106+
| _ -> ())
107+
| _ -> ()
108+
109+
module A = struct
110+
(** ego tid * must-lockset * creation-lockset *)
111+
include Printable.Prod3 (TID) (Lockset) (G)
112+
113+
let name () = "creationLockset"
114+
115+
(** checks if [cl1] has a mapping ([tp1] |-> [ls1])
116+
such that [ls1] and [ls2] are not disjoint and [tp1] != [t2]
117+
@param cl1 creation-lockset of thread [t1] at first program point
118+
@param t2 thread id at second program point
119+
@param ls2 lockset at second program point
120+
@returns whether [t1] must be running mutually exclusive with second program point
121+
*)
122+
let one_protected_inter_threaded_other_intra_threaded cl1 t2 ls2 =
123+
G.exists (fun tp1 ls1 -> not (Lockset.disjoint ls1 ls2 || TID.equal tp1 t2)) cl1
124+
125+
(** checks if [cl1] has a member ([tp1] |-> [ls1]) and [cl2] has a member ([tp2] |-> [ls2])
126+
such that [ls1] and [ls2] are not disjoint and [tp1] != [tp2]
127+
@param cl1 creation-lockset of first thread [t1]
128+
@param cl2 creation-lockset of second thread [t2]
129+
@returns whether [t1] and [t2] must be running mutually exclusive
130+
*)
131+
let both_protected_inter_threaded cl1 cl2 =
132+
G.exists (one_protected_inter_threaded_other_intra_threaded cl1) cl2
133+
134+
let may_race (t1, ls1, cl1) (t2, ls2, cl2) =
135+
not
136+
(both_protected_inter_threaded cl1 cl2
137+
|| one_protected_inter_threaded_other_intra_threaded cl1 t2 ls2
138+
|| one_protected_inter_threaded_other_intra_threaded cl2 t1 ls1)
139+
140+
let should_print (_t, _ls, cl) = not @@ G.is_empty cl
141+
end
142+
143+
let access man _ =
144+
let tid_lifted = man.ask Queries.CurrentThreadId in
145+
match tid_lifted with
146+
| `Lifted tid ->
147+
let lockset = man.ask Queries.MustLockset in
148+
let creation_lockset = man.global tid in
149+
tid, lockset, creation_lockset
150+
| _ -> ThreadIdDomain.UnknownThread, Lockset.empty (), G.empty ()
151+
end
152+
153+
let _ =
154+
MCP.register_analysis
155+
~dep:[ "threadid"; "mutex"; "threadJoins"; "threadDescendants" ]
156+
(module Spec : MCPSpec)

src/analyses/mutexGhosts.ml

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,6 @@ struct
6161
let create_update = create_threadcreate
6262
end
6363

64-
let mustlock_of_addr (addr: LockDomain.Addr.t): LockDomain.MustLock.t option =
65-
match addr with
66-
| Addr mv when LockDomain.Mval.is_definite mv -> Some (LockDomain.MustLock.of_mval mv)
67-
| _ -> None
68-
6964
let event man e oman =
7065
let verifier_atomic_addr = LockDomain.Addr.of_var LibraryFunctions.verifier_atomic_var in
7166
begin match e with
@@ -78,7 +73,7 @@ struct
7873
Locked.iter (fun lock ->
7974
Option.iter (fun lock ->
8075
man.sideg (V.lock lock) (G.create_lock true)
81-
) (mustlock_of_addr lock)
76+
) (LockDomain.MustLock.of_addr lock)
8277
) locked
8378
);
8479
)
@@ -91,7 +86,7 @@ struct
9186
Locked.iter (fun lock ->
9287
Option.iter (fun lock ->
9388
man.sideg (V.lock lock) (G.create_lock true)
94-
) (mustlock_of_addr lock)
89+
) (LockDomain.MustLock.of_addr lock)
9590
) unlocked
9691
);
9792
)
@@ -128,7 +123,7 @@ struct
128123
let (locked, unlocked, multithread) = G.node (man.global (V.node node)) in
129124
let variables' =
130125
Locked.fold (fun l acc ->
131-
match mustlock_of_addr l with
126+
match LockDomain.MustLock.of_addr l with
132127
| Some l when ghost_var_available man (Locked l) ->
133128
let variable = WitnessGhost.variable' (Locked l) in
134129
VariableSet.add variable acc
@@ -138,7 +133,7 @@ struct
138133
in
139134
let updates =
140135
Locked.fold (fun l acc ->
141-
match mustlock_of_addr l with
136+
match LockDomain.MustLock.of_addr l with
142137
| Some l when ghost_var_available man (Locked l) ->
143138
let update = WitnessGhost.update' (Locked l) GoblintCil.one in
144139
update :: acc
@@ -148,7 +143,7 @@ struct
148143
in
149144
let updates =
150145
Unlocked.fold (fun l acc ->
151-
match mustlock_of_addr l with
146+
match LockDomain.MustLock.of_addr l with
152147
| Some l when ghost_var_available man (Locked l) ->
153148
let update = WitnessGhost.update' (Locked l) GoblintCil.zero in
154149
update :: acc

src/analyses/threadDescendants.ml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
(** thread descendants analysis [threadDescendants]
2+
flow-insensitive construction of descendants may-set for every thread
3+
*)
4+
5+
open Analyses
6+
module TID = ThreadIdDomain.Thread
7+
8+
module Spec = struct
9+
include IdentityUnitContextsSpec
10+
module D = Lattice.Unit
11+
12+
module V = struct
13+
include TID
14+
include StdV
15+
end
16+
17+
module G = ConcDomain.ThreadSet
18+
19+
let name () = "threadDescendants"
20+
let startstate _ = D.bot ()
21+
let exitstate _ = D.bot ()
22+
23+
let query man (type a) (x : a Queries.t) : a Queries.result =
24+
match x with
25+
| Queries.DescendantThreads t ->
26+
let children = man.global t in
27+
(G.fold
28+
(fun e acc -> G.union (man.ask @@ Queries.DescendantThreads e) acc)
29+
children
30+
children
31+
: G.t)
32+
| _ -> Queries.Result.top x
33+
34+
let threadspawn man ~multiple lval f args fman =
35+
let tid_lifted = man.ask Queries.CurrentThreadId in
36+
let child_tid_lifted = fman.ask Queries.CurrentThreadId in
37+
match tid_lifted, child_tid_lifted with
38+
| `Lifted tid, `Lifted child_tid -> man.sideg tid (G.singleton child_tid)
39+
| _ -> ()
40+
end
41+
42+
let _ = MCP.register_analysis ~dep:[ "threadid" ] (module Spec : MCPSpec)

src/cdomains/lockDomain.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ struct
2929
let of_mval ((v, o): Mval.t): t =
3030
(v, Offset.Poly.map_indices (fun i -> IndexDomain.to_int i |> Option.get) o)
3131

32+
let of_addr (addr : Addr.t) : t option =
33+
match addr with
34+
| Addr mv when Mval.is_definite mv -> Some (of_mval mv)
35+
| _ -> None
36+
3237
let to_mval ((v, o): t): Mval.t =
3338
(v, Offset.Poly.map_indices (IndexDomain.of_int (Cilfacade.ptrdiff_ikind ())) o)
3439
end

src/domains/queries.ml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ type _ t =
146146
| YamlEntryGlobal: Obj.t * YamlWitnessType.Task.t -> YS.t t (** YAML witness entries for a global unknown ([Obj.t] represents [Spec.V.t]) and YAML witness task. *)
147147
| GhostVarAvailable: WitnessGhostVar.t -> MayBool.t t
148148
| InvariantGlobalNodes: NS.t t (** Nodes where YAML witness flow-insensitive invariants should be emitted as location invariants (if [witness.invariant.flow_insensitive-as] is configured to do so). *) (* [Spec.V.t] argument (as [Obj.t]) could be added, if this should be different for different flow-insensitive invariants. *)
149+
| DescendantThreads: ThreadIdDomain.Thread.t -> ConcDomain.ThreadSet.t t
149150

150151
type 'a result = 'a
151152

@@ -221,6 +222,7 @@ struct
221222
| YamlEntryGlobal _ -> (module YS)
222223
| GhostVarAvailable _ -> (module MayBool)
223224
| InvariantGlobalNodes -> (module NS)
225+
| DescendantThreads _ -> (module ConcDomain.ThreadSet)
224226

225227
(** Get bottom result for query. *)
226228
let bot (type a) (q: a t): a result =
@@ -295,6 +297,7 @@ struct
295297
| YamlEntryGlobal _ -> YS.top ()
296298
| GhostVarAvailable _ -> MayBool.top ()
297299
| InvariantGlobalNodes -> NS.top ()
300+
| DescendantThreads _ -> ConcDomain.ThreadSet.top ()
298301
end
299302

300303
(* The type any_query can't be directly defined in Any as t,
@@ -366,6 +369,7 @@ struct
366369
| Any (MustProtectingLocks _) -> 61
367370
| Any (GhostVarAvailable _) -> 62
368371
| Any InvariantGlobalNodes -> 63
372+
| Any (DescendantThreads _) -> 64
369373

370374
let rec compare a b =
371375
let r = Stdlib.compare (order a) (order b) in
@@ -425,6 +429,7 @@ struct
425429
| Any (MaySignedOverflow e1), Any (MaySignedOverflow e2) -> CilType.Exp.compare e1 e2
426430
| Any (GasExhausted f1), Any (GasExhausted f2) -> CilType.Fundec.compare f1 f2
427431
| Any (GhostVarAvailable v1), Any (GhostVarAvailable v2) -> WitnessGhostVar.compare v1 v2
432+
| Any (DescendantThreads t1), Any (DescendantThreads t2) -> ThreadIdDomain.Thread.compare t1 t2
428433
(* only argumentless queries should remain *)
429434
| _, _ -> Stdlib.compare (order a) (order b)
430435

@@ -472,6 +477,7 @@ struct
472477
| Any (MaySignedOverflow e) -> CilType.Exp.hash e
473478
| Any (GasExhausted f) -> CilType.Fundec.hash f
474479
| Any (GhostVarAvailable v) -> WitnessGhostVar.hash v
480+
| Any (DescendantThreads t) -> ThreadIdDomain.Thread.hash t
475481
(* IterSysVars: *)
476482
(* - argument is a function and functions cannot be compared in any meaningful way. *)
477483
(* - doesn't matter because IterSysVars is always queried from outside of the analysis, so MCP's query caching is not done for it. *)
@@ -540,6 +546,7 @@ struct
540546
| Any (GasExhausted f) -> Pretty.dprintf "GasExhausted %a" CilType.Fundec.pretty f
541547
| Any (GhostVarAvailable v) -> Pretty.dprintf "GhostVarAvailable %a" WitnessGhostVar.pretty v
542548
| Any InvariantGlobalNodes -> Pretty.dprintf "InvariantGlobalNodes"
549+
| Any (DescendantThreads t) -> Pretty.dprintf "DescendantThreads %a" ThreadIdDomain.Thread.pretty t
543550
end
544551

545552
let to_value_domain_ask (ask: ask) =

src/goblint_lib.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ module ThreadAnalysis = ThreadAnalysis
120120
module ThreadJoins = ThreadJoins
121121
module MHPAnalysis = MHPAnalysis
122122
module ThreadReturn = ThreadReturn
123+
module ThreadDescendants = ThreadDescendants
123124

124125
(** {3 Other} *)
125126

@@ -131,6 +132,7 @@ module PthreadSignals = PthreadSignals
131132
module PthreadBarriers = PthreadBarriers
132133
module ExtractPthread = ExtractPthread
133134
module PthreadOnce = PthreadOnce
135+
module CreationLockset = CreationLockset
134136

135137
(** {2 Longjmp}
136138
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// PARAM: --set ana.activated[+] threadJoins --set ana.activated[+] threadDescendants --set ana.activated[+] creationLockset
2+
#include <pthread.h>
3+
4+
int global = 0;
5+
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
6+
pthread_t id1, id2;
7+
8+
void *t1(void *arg) {
9+
pthread_mutex_lock(&mutex);
10+
global++; // NORACE
11+
pthread_mutex_unlock(&mutex);
12+
return NULL;
13+
}
14+
15+
void *t2(void *arg) { // t2 is protected by mutex locked in main thread
16+
global++; // NORACE
17+
return NULL;
18+
}
19+
20+
int main(void) {
21+
pthread_create(&id1, NULL, t1, NULL);
22+
pthread_mutex_lock(&mutex);
23+
pthread_create(&id2, NULL, t2, NULL);
24+
pthread_join(id2, NULL);
25+
pthread_mutex_unlock(&mutex);
26+
return 0;
27+
}

0 commit comments

Comments
 (0)