-
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
Draft
dabund24
wants to merge
31
commits into
goblint:master
Choose a base branch
from
dabund24:descendant-locksets
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+698
−32
Draft
Changes from 24 commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
4f611d5
mustlock history analysis
dabund24 d4189b4
initial version of descendant lockset analysis
dabund24 4d5fee2
first descendant lockset racefree tests
dabund24 2cc4be9
first descendant lockset racing tests
dabund24 470279f
descendant lockset racing tests with multiple thread creations
dabund24 1f883f2
creation lockset query
dabund24 bb16da6
global descendant locksets
dabund24 8f46eb3
regression tests for global descendant locksets
dabund24 61cdb71
descendant lockset tests involving multiple mutexes
dabund24 67a62bd
Merge branch 'master' into descendant-locksets
dabund24 c8cdf89
fix incorrect indentatoin in regression test
dabund24 6c68a6c
add missing return statement to regression test
dabund24 6020267
add missing mutex initializations
dabund24 5bf536d
remove redundant whitespace in creation lockset analysis
dabund24 bd388a2
remove unused tid function parameter in `unlock` and `unknown_unlock`…
dabund24 66ad3f1
modify global domain to use `D` instances as values
dabund24 d4aec37
Merge branch 'master' into descendant-locksets
dabund24 743092a
recursive mutex regression tests for descendant locksets
dabund24 b161fed
Merge branch 'master' into descendant-locksets
dabund24 3461214
use `MapTop` instead of `MapBot` for inner map of descendant lockset …
dabund24 717b52f
some documentation in `descendantLockset` analysis
dabund24 ee18c9a
improve printing of `descendantLockset` analysis
dabund24 9f5b264
documentation for `mustlockHistory` analysis
dabund24 83d6f5a
remove unused dependencies from mustlock history analysis
dabund24 6785efa
Merge branch 'master' into descendant-locksets
dabund24 a71d29e
refactor functions related to thread descendants
dabund24 be913ba
locally abstract type for `happens_before` function in `descendantLoc…
dabund24 624254b
also print mustlock history in `A` of descendant lockset analysis
dabund24 30d7daf
remove side effects analysis for descendant locksets
dabund24 9b40093
skip tests involving global descendant lockset analysis
dabund24 ba0cac3
Merge branch 'master' into descendant-locksets
dabund24 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | ||
| 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 | ||
|
|
||
| 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 *) | ||
| 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
Show resolved
Hide resolved
|
||
|
|
||
| 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 -> | ||
| 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
Show resolved
Hide resolved
|
||
|
|
||
| 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) | ||
|
|
||
| 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.
Show resolved
Hide resolved
|
||
| (module Spec : MCPSpec) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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.
Show resolved
Hide 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) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| } |
27 changes: 27 additions & 0 deletions
27
tests/regression/53-races-mhp/41-dl_lock_in_intermediate_thread_racefree.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.