-
Notifications
You must be signed in to change notification settings - Fork 88
HB-relationship involving thread creations while mutexes are held #1913
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 24 commits
4f611d5
d4189b4
4d5fee2
2cc4be9
470279f
1f883f2
bb16da6
8f46eb3
61cdb71
67a62bd
c8cdf89
6c68a6c
6020267
5bf536d
bd388a2
66ad3f1
d4aec37
743092a
b161fed
3461214
717b52f
ee18c9a
9f5b264
83d6f5a
6785efa
a71d29e
be913ba
624254b
30d7daf
9b40093
ba0cac3
9e8399e
6827d4d
ac2b66f
b0e6f4b
6dd1f3c
596cb68
7e4f3d8
f3ba129
2ab8e7f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| (** descendant lockset analysis [descendantLockset] | ||
| analyzes a happened-before relationship related to thread creations with mutexes held. | ||
|
|
||
| Enabling [creationLockset] may improve the precision of this analysis. | ||
|
|
||
| @see https://github.com/goblint/analyzer/pull/1923 | ||
| *) | ||
|
|
||
| open Analyses | ||
| module TID = ThreadIdDomain.Thread | ||
| module TIDs = ConcDomain.ThreadSet | ||
| module Lockset = LockDomain.MustLockset | ||
| module TidToLocksetMapTop = MapDomain.MapTop (TID) (Lockset) | ||
|
|
||
| module Spec = struct | ||
| include IdentityUnitContextsSpec | ||
|
|
||
| (** [{ t_d |-> L }] | ||
|
|
||
| [t_d] was transitively created with all members of [L] held. | ||
| Additionally, no member of [L] could have been unlocked after the creation of [t_d] | ||
| *) | ||
| module D = MapDomain.MapBot (TID) (Lockset) | ||
|
|
||
| (** [{ t_0 |-> { t_d |-> L } }] | ||
|
|
||
| [{ t_d |-> L }] is the descendant lockset valid for the [V] value, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is |
||
| because [t_d] was created in [t_0] with the lockset being a superset of L. | ||
|
|
||
| We suspect [MapBot] to suffice for the inner map. To ensure soundness, we use [MapTop] instead | ||
| *) | ||
| module G = MapDomain.MapBot (TID) (TidToLocksetMapTop) | ||
|
|
||
| module V = struct | ||
| include TID | ||
| include StdV | ||
| end | ||
|
Comment on lines
+31
to
+34
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we have four instances of this now, maybe add a |
||
|
|
||
| let name () = "descendantLockset" | ||
| let startstate _ = D.empty () | ||
| let exitstate _ = D.empty () | ||
|
|
||
| (** reflexive-transitive closure of child relation applied to [tid] and | ||
| filtered to only include threads, where [tid] is a must-ancestor | ||
| @param man any man | ||
| @param tid *) | ||
|
dabund24 marked this conversation as resolved.
Outdated
|
||
| let must_ancestor_descendants_closure man tid = | ||
| let descendants = man.ask @@ Queries.DescendantThreads tid in | ||
| let must_ancestors_descendants = TIDs.filter (TID.must_be_ancestor tid) descendants in | ||
| TIDs.add tid must_ancestors_descendants | ||
|
dabund24 marked this conversation as resolved.
Outdated
|
||
|
|
||
| let threadspawn_contribute_globals man tid must_ancestor_descendants = | ||
| let descendant_lockset = man.local in | ||
| let contribute_for_descendant t_d = | ||
| let creation_lockset = man.ask @@ Queries.CreationLockset t_d in | ||
| let to_contribute = | ||
| D.fold | ||
| (fun t_l l_dl acc -> | ||
| let l_cl = Queries.CL.find tid creation_lockset in | ||
| let l_inter = Lockset.inter l_cl l_dl in | ||
| TidToLocksetMapTop.add t_l l_inter acc) | ||
| descendant_lockset | ||
| (TidToLocksetMapTop.empty ()) | ||
| in | ||
| man.sideg t_d (G.singleton tid to_contribute) | ||
| in | ||
| TIDs.iter contribute_for_descendant must_ancestor_descendants | ||
|
|
||
| let threadspawn_compute_local_contribution man tid must_ancestor_descendants = | ||
| let lockset = man.ask Queries.MustLockset in | ||
| TIDs.fold | ||
| (fun t_d -> D.join (D.singleton t_d lockset)) | ||
| must_ancestor_descendants | ||
| man.local | ||
|
|
||
| let threadspawn man ~multiple lval f args fman = | ||
| let tid_lifted = man.ask Queries.CurrentThreadId in | ||
| let child_tid_lifted = fman.ask Queries.CurrentThreadId in | ||
| match tid_lifted, child_tid_lifted with | ||
| | `Lifted tid, `Lifted child_tid when TID.must_be_ancestor tid child_tid -> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This implicitly checks uniqueness of I was wondering about the case where two threads get the same non-unique id, but for one of the creations no mutex is held while one is held for the other? Is this handled by the lockset being a must lockset? Do you have a test for this? |
||
| let must_ancestor_descendants = must_ancestor_descendants_closure fman child_tid in | ||
| threadspawn_contribute_globals man tid must_ancestor_descendants; | ||
| threadspawn_compute_local_contribution man tid must_ancestor_descendants | ||
| | _ -> man.local | ||
|
|
||
| let get_must_ancestor_running_descendants man tid = | ||
| let may_created_tids = man.ask Queries.CreatedThreads in | ||
| let may_must_ancestor_created_tids = | ||
| TIDs.filter (TID.must_be_ancestor tid) may_created_tids | ||
| in | ||
| let may_transitively_created_tids = | ||
| TIDs.fold | ||
| (fun child_tid acc -> | ||
| TIDs.union acc (must_ancestor_descendants_closure man child_tid)) | ||
| may_must_ancestor_created_tids | ||
| (TIDs.empty ()) | ||
| in | ||
| let must_joined_tids = man.ask Queries.MustJoinedThreads in | ||
| TIDs.diff may_transitively_created_tids must_joined_tids | ||
|
dabund24 marked this conversation as resolved.
Outdated
|
||
|
|
||
| let unlock man possibly_running_tids lock = | ||
| TIDs.fold | ||
| (fun des_tid -> | ||
| let old_value = D.find des_tid man.local in | ||
| let new_value = Lockset.remove lock old_value in | ||
| D.add des_tid new_value) | ||
| possibly_running_tids | ||
| (D.empty ()) | ||
|
|
||
| let unknown_unlock man possibly_running_tids = | ||
| TIDs.fold | ||
| (fun des_tid -> D.add des_tid (Lockset.empty ())) | ||
| possibly_running_tids | ||
| (D.empty ()) | ||
|
|
||
| let event man e _ = | ||
| match e with | ||
| | Events.Unlock addr -> | ||
| let tid_lifted = man.ask Queries.CurrentThreadId in | ||
| (match tid_lifted with | ||
| | `Lifted tid -> | ||
| let possibly_running_tids = get_must_ancestor_running_descendants man tid in | ||
| let lock_opt = LockDomain.MustLock.of_addr addr in | ||
| (match lock_opt with | ||
| | Some lock -> unlock man possibly_running_tids lock | ||
| | None -> unknown_unlock man possibly_running_tids) | ||
| | _ -> man.local) | ||
| | _ -> man.local | ||
|
|
||
| module A = struct | ||
| module DlProd = Printable.Prod (D) (G) | ||
|
|
||
| (** ego tid * lock history * (local descendant lockset * global descendant lockset) *) | ||
| include Printable.Prod3 (TID) (Queries.LH) (DlProd) | ||
|
|
||
| let happens_before (t1, dl1) (t2, lh2) = | ||
| let locks_held_creating_t2 = D.find t2 dl1 in | ||
| if Lockset.is_bot locks_held_creating_t2 | ||
| then false | ||
| else ( | ||
| let relevant_lh2_threads = | ||
| Lockset.fold | ||
| (fun lock -> TIDs.union (Queries.LH.find lock lh2)) | ||
| locks_held_creating_t2 | ||
| (TIDs.empty ()) | ||
| in | ||
| TIDs.exists | ||
| (fun t_lh -> | ||
| TID.must_be_ancestor t1 t_lh | ||
| && (TID.equal t_lh t2 || TID.must_be_ancestor t_lh t2)) | ||
| relevant_lh2_threads) | ||
|
Comment on lines
+115
to
+128
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The indentation is a little strange here. Also, I don't think you need the |
||
|
|
||
| let happens_before_global dlg1 (t2, lh2) = | ||
| G.exists | ||
| (fun t dl_map_top -> | ||
| let dl_map_bot = TidToLocksetMapTop.fold D.add dl_map_top (D.empty ()) in (* convert MapTop to MapBot *) | ||
| happens_before (t, dl_map_bot) (t2, lh2)) | ||
| dlg1 | ||
|
|
||
| let may_race (t1, lh1, (dl1, dlg1)) (t2, lh2, (dl2, dlg2)) = | ||
| not | ||
| (happens_before (t1, dl1) (t2, lh2) | ||
| || happens_before (t2, dl2) (t1, lh1) | ||
| || happens_before_global dlg1 (t2, lh2) | ||
| || happens_before_global dlg2 (t1, lh1)) | ||
|
|
||
| (* only descendant locksets need to be printed *) | ||
| let pretty () (_, _, dl) = DlProd.pretty () dl | ||
| let show (_, _, dl) = DlProd.show dl | ||
| let to_yojson (_, _, dl) = DlProd.to_yojson dl | ||
| let printXml f (_, _, dl) = DlProd.printXml f dl | ||
|
|
||
| let should_print (_, _, (dl, dlg)) = | ||
| let ls_not_empty _ ls = not @@ Lockset.is_empty ls in | ||
| D.exists (ls_not_empty) dl | ||
| || G.exists (fun _ -> TidToLocksetMapTop.exists (ls_not_empty)) dlg | ||
| end | ||
|
|
||
| let access man _ = | ||
| let lh = man.ask Queries.MustlockHistory in | ||
| let tid_lifted = man.ask Queries.CurrentThreadId in | ||
| match tid_lifted with | ||
| | `Lifted tid -> tid, lh, (man.local, man.global tid) | ||
| | _ -> ThreadIdDomain.UnknownThread, Queries.LH.empty (), (D.empty (), G.empty ()) | ||
| end | ||
|
|
||
| let _ = | ||
| MCP.register_analysis | ||
| ~dep:[ "threadid"; "mutex"; "threadJoins"; "threadDescendants"; "mustlockHistory" ] | ||
|
michael-schwarz marked this conversation as resolved.
|
||
| (module Spec : MCPSpec) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| (** must-lock history analysis [mustlockHistory] | ||
| collects for locks, in which threads a lock operation must have happened | ||
| before reaching the current program point. | ||
|
|
||
| @see https://github.com/goblint/analyzer/pull/1923 | ||
| *) | ||
|
|
||
| open Analyses | ||
| module TIDs = SetDomain.Reverse (ConcDomain.ThreadSet) | ||
| module Lock = LockDomain.MustLock | ||
|
|
||
| module Spec = struct | ||
|
dabund24 marked this conversation as resolved.
|
||
| include IdentityUnitContextsSpec | ||
|
|
||
| (** [{ l |-> T }] | ||
|
|
||
| [l] must have been in all members of [T]. | ||
| *) | ||
| module D = Queries.LH | ||
|
|
||
| let name () = "mustlockHistory" | ||
| let startstate _ = D.empty () | ||
| let exitstate _ = D.empty () | ||
|
|
||
| let lock man tid lock = | ||
| let old_threadset = D.find lock man.local in | ||
| let new_threadset = TIDs.add tid old_threadset in | ||
| D.add lock new_threadset man.local | ||
|
|
||
| let event man e _ = | ||
| match e with | ||
| (* we only handle exclusive locks here *) | ||
| | Events.Lock (addr, true) -> | ||
| let tid_lifted = man.ask Queries.CurrentThreadId in | ||
| let lock_opt = Lock.of_addr addr in | ||
| (match tid_lifted, lock_opt with | ||
| | `Lifted tid, Some l -> lock man tid l | ||
| | _ -> man.local) | ||
| | _ -> man.local | ||
|
|
||
| let query man (type a) (x : a Queries.t) : a Queries.result = | ||
| match x with | ||
| | Queries.MustlockHistory -> (man.local : D.t) | ||
| | _ -> Queries.Result.top x | ||
| end | ||
|
|
||
| let _ = MCP.register_analysis ~dep:[ "threadid" ] (module Spec : MCPSpec) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // PARAM: --set ana.activated[+] threadJoins --set ana.activated[+] threadDescendants --set ana.activated[+] mustlockHistory --set ana.activated[+] descendantLockset | ||
| #include <pthread.h> | ||
|
|
||
| int global = 0; | ||
| pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; | ||
| pthread_t id1; | ||
|
|
||
| void *t1(void *arg) { | ||
| pthread_mutex_lock(&mutex); | ||
| // everything from here must happen after unlock in main | ||
| pthread_mutex_unlock(&mutex); | ||
| global++; // NORACE | ||
| return NULL; | ||
| } | ||
|
|
||
| int main(void) { | ||
| pthread_mutex_lock(&mutex); | ||
| pthread_create(&id1, NULL, t1, NULL); | ||
| global++; // NORACE | ||
| pthread_mutex_unlock(&mutex); | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // PARAM: --set ana.activated[+] threadJoins --set ana.activated[+] threadDescendants --set ana.activated[+] mustlockHistory --set ana.activated[+] descendantLockset | ||
| #include <pthread.h> | ||
|
|
||
| int global = 0; | ||
| pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; | ||
| pthread_t id1, id2; | ||
|
|
||
| void *t2(void *arg) { | ||
| global++; // NORACE | ||
| return NULL; | ||
| } | ||
|
|
||
| void *t1(void *arg) { | ||
| pthread_mutex_lock(&mutex); | ||
| // everything from here must happen after unlock in main | ||
| pthread_create(&id2, NULL, t2, NULL); | ||
| pthread_mutex_unlock(&mutex); | ||
| return NULL; | ||
| } | ||
|
|
||
| int main(void) { | ||
| pthread_mutex_lock(&mutex); | ||
| pthread_create(&id1, NULL, t1, NULL); | ||
| global++; // NORACE | ||
| pthread_mutex_unlock(&mutex); | ||
| return 0; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you should push your thesis to https://github.com/goblint/theses and then reference it here (and add something like "available upon request") until there's a formal publication.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same for the other analyses from your thesis.