Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
7831cbf
add inter-procedural lock c files
dabund24 Oct 17, 2025
3ef7082
add lock-fork hb relationship c file
dabund24 Oct 17, 2025
9d88584
use pthread_create() and pthread_join() instead of race macros for in…
dabund24 Nov 5, 2025
b7d0d35
activate creationLockset analysis for inter-threaded lock regressions…
dabund24 Nov 5, 2025
93a513a
initial version of creationLockset analysis
dabund24 Nov 5, 2025
946536b
AncestorLocksetSpec as common base module
dabund24 Nov 7, 2025
a78e44c
initial version of TaintedCreationLockset analysis
dabund24 Nov 7, 2025
8ba9094
use thread domain instead of lifted thread domain
dabund24 Nov 7, 2025
ead4843
add threadJoins as dependency for TaintedCreationLockset analysis
dabund24 Nov 7, 2025
73e6d9c
initial version of transitive descendants analysis
dabund24 Nov 7, 2025
f212c45
some comments in transitiveDescendats analysis
dabund24 Nov 7, 2025
4633ec7
query for descendant analysis
dabund24 Nov 11, 2025
3f3e2f3
get rid of unnecessary match expression
dabund24 Nov 15, 2025
691cdfd
MayCreationLockset query
dabund24 Nov 16, 2025
61d5c3f
InterThreadedLockset query
dabund24 Nov 16, 2025
e1719b2
fix incorrect query answer type in transitive descendants analysis
dabund24 Nov 16, 2025
8720b23
cartesian product helper functions
dabund24 Nov 18, 2025
7c6e5d9
remove unused function from TaintedCreationLocksetSpec
dabund24 Nov 18, 2025
61a48dc
correct comment in tainted lockset analysis
dabund24 Nov 18, 2025
31ceff8
replace threadset and lockset module references with shorthand
dabund24 Nov 18, 2025
450d349
function for getting currently running tids
dabund24 Nov 18, 2025
7d1fa1a
inter-threaded lockset A module
dabund24 Nov 19, 2025
3c52c76
use topped set for global domain in AncestorLocksetSpec
dabund24 Nov 19, 2025
8b1727f
replace comparison operators with equals function of domains
dabund24 Nov 19, 2025
b0751dc
add creationLockset analysis to dependencies of taintedCreationLockse…
dabund24 Nov 19, 2025
09f28ba
fix regression test files
dabund24 Nov 19, 2025
ea5a72a
move test files to better locations
dabund24 Nov 19, 2025
0b0fae8
remove unused inter threaded lockset query
dabund24 Nov 20, 2025
3a5513d
hash descendant thread query param
dabund24 Nov 20, 2025
cecf244
transitive version of (tainted) creation locksets
dabund24 Nov 20, 2025
55184fe
add race and transitive descendants analyses as dependencies to creat…
dabund24 Nov 20, 2025
611a91e
regression tests for transitive creation locksets
dabund24 Nov 20, 2025
c1ad680
rename regression test for second case
dabund24 Nov 20, 2025
70dabb7
add thread param to MayCreationLockset query
dabund24 Nov 20, 2025
0e63731
handle unlock of unknown mutex
dabund24 Nov 20, 2025
3514a37
edit some comments
dabund24 Nov 20, 2025
19ce960
Merge branch 'goblint:master' into master
dabund24 Nov 20, 2025
61b9609
remove redundant must-ancestor check
dabund24 Dec 4, 2025
52ce9f0
fix and update some comments
dabund24 Dec 4, 2025
7f4e531
minimize contributions to tcl when unlock of unknown thread is encoun…
dabund24 Dec 4, 2025
2158f23
align naming style of A module with other modules
dabund24 Dec 4, 2025
e531002
Merge branch 'master' into master
dabund24 Dec 4, 2025
85aad48
remove irrelevant test case
dabund24 Dec 4, 2025
4b9c2c7
add new modules to goblint_lib
dabund24 Dec 4, 2025
2b62168
add params to tests
dabund24 Dec 4, 2025
dbf73f1
remove comment/question concerning contexts
dabund24 Dec 5, 2025
e1299ea
align hash calls for threads in queries with other hash calls
dabund24 Dec 5, 2025
87dde05
move address to must lock conversion from mutex ghosts/creation locks…
dabund24 Dec 5, 2025
b8416ef
should_print in A module
dabund24 Dec 5, 2025
13443f9
impose conditions on config before running creation lockset analyses
dabund24 Dec 5, 2025
f1efd32
remove bad semicolon
dabund24 Dec 5, 2025
6ec28ca
ambiguous thread creation test cases
dabund24 Dec 8, 2025
1879e7e
more racefree test cases
dabund24 Dec 8, 2025
5b97ec4
more racing test cases
dabund24 Dec 8, 2025
fc22ec0
use "RACE!" instead of "RACE"
dabund24 Dec 8, 2025
4ac477e
Merge branch 'master' into master
dabund24 Dec 8, 2025
695081c
remove unused LibraryFunctions import
dabund24 Dec 9, 2025
5bf35ca
add query parameters to result of pretty function
dabund24 Dec 9, 2025
8d5928e
add missing `return NULL;`
dabund24 Dec 9, 2025
873ff0c
remove redundant `;;`
dabund24 Dec 9, 2025
b495fa9
undo accidental changes to `raceAnalysis.ml`
dabund24 Dec 9, 2025
3c45dff
remove redundant `;;` in `creationLockset.ml`
dabund24 Dec 9, 2025
6c2c849
remove debug statements accidentally committed
dabund24 Dec 9, 2025
1db14cb
add ambiguous context regression test
dabund24 Dec 10, 2025
099f742
change global domain for creation lockset analysis
dabund24 Dec 10, 2025
850e1cb
remove creation lockset query
dabund24 Dec 10, 2025
76c14dd
remove config constraints for creation lockset analysis
dabund24 Dec 10, 2025
1f3cdef
remove query function from creation lockset analysis
dabund24 Dec 10, 2025
72afe4a
enforce must-ancestor property in unlock transfer function
dabund24 Dec 10, 2025
3b494d9
remove some semi-colons
dabund24 Dec 10, 2025
583a41f
fix comments for test files
dabund24 Dec 10, 2025
744f584
reorder some statements in event transition function
dabund24 Dec 11, 2025
648de9a
comment on applying setminus to bottom
dabund24 Dec 11, 2025
772cf03
move domain type dafinition back from queries to analysis
dabund24 Dec 11, 2025
a8479fe
move comment explaining the analysis to top of file
dabund24 Dec 11, 2025
14d195f
fix an outdated comment
dabund24 Dec 11, 2025
a0c2446
rename descendants analysis
dabund24 Dec 11, 2025
048ca71
make threadspawn transfer function in descendants analysis a little l…
dabund24 Dec 11, 2025
a677314
top comment for thread descendants analysis
dabund24 Dec 11, 2025
97a6967
re-add an empty line, which was removed in a previous commit
dabund24 Dec 11, 2025
2c9c39d
remove redundant and inaccurate comment
dabund24 Dec 11, 2025
c36d6d1
must_ancestors function for thread ids
dabund24 Dec 12, 2025
3c1a51f
must_ancestors test
dabund24 Dec 12, 2025
32c76cd
Merge branch 'master' into master
dabund24 Dec 12, 2025
34f4e9f
change return type of must_ancestors to not be an option
dabund24 Dec 14, 2025
5403dfd
initial version of alternative analysis
dabund24 Dec 14, 2025
7770141
tests for alternative analysis
dabund24 Dec 14, 2025
169b60f
compare functions for queries
dabund24 Dec 15, 2025
fe3c133
give tests for alternative implementation non-unique names
dabund24 Dec 15, 2025
edda5d8
one more norace test case
dabund24 Dec 15, 2025
b011914
consistant * style in tests
dabund24 Dec 15, 2025
8aa1490
circle -> cycle
dabund24 Dec 15, 2025
57cf4a8
clean up creation lockset alternative file
dabund24 Dec 15, 2025
82ecd93
fix dependency analyses names
dabund24 Dec 15, 2025
135ce02
update module comments for alternative creation lockset analysis
dabund24 Dec 15, 2025
2054f59
cl -> il in A module of alternative analysis
dabund24 Dec 15, 2025
6b8c3f3
restructure unlock and unknown unlock of alternative creation lockset…
dabund24 Dec 15, 2025
db4cdf9
Merge branch 'master' into master
dabund24 Dec 15, 2025
0df9c76
update outdated comment in thread id domain
dabund24 Dec 15, 2025
00ca4df
norace -> racefree aligning test names with those of other tests
dabund24 Dec 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions src/analyses/creationLockset.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
(** creation lockset analysis [creationLockset]
constructs edges on the graph over all threads, where the edges are labelled with must-locksets:
(t_1) ---L--> (t_0) means that t_1 is protected by all members of L from t_0

@see https://github.com/goblint/analyzer/pull/1865
*)

open Analyses
module TID = ThreadIdDomain.Thread
module TIDs = ConcDomain.ThreadSet
module LID = LockDomain.MustLock
module LIDs = LockDomain.MustLockset

module Spec = struct
include IdentityUnitContextsSpec
module D = Lattice.Unit

module V = struct
include TID
include StdV
end

module G = MapDomain.MapBot (TID) (LIDs)

let name () = "creationLockset"
let startstate _ = D.bot ()
let exitstate _ = D.bot ()

(** register a global contribution: global.[child_tid] \supseteq [to_contribute]
@param man man at program point
@param to_contribute new edges from [child_tid] to ego thread to register
@param child_tid
*)
let contribute_locks man to_contribute child_tid = man.sideg child_tid to_contribute

(** reflexive-transitive closure of child relation applied to [tid]
and filtered to only include threads, where [tid] is a must-ancestor
@param ask any ask
@param tid
*)
let unique_descendants_closure (ask : Queries.ask) tid =
let descendants = ask.f @@ Queries.DescendantThreads tid in
let unique_descendants = TIDs.filter (TID.must_be_ancestor tid) descendants in
TIDs.add tid unique_descendants

let threadspawn man ~multiple lval f args fman =
let ask = ask_of_man man in
let tid_lifted = ask.f Queries.CurrentThreadId in
let child_ask = ask_of_man fman in
let child_tid_lifted = child_ask.f Queries.CurrentThreadId in
match tid_lifted, child_tid_lifted with
| `Lifted tid, `Lifted child_tid when TID.must_be_ancestor tid child_tid ->
let unique_descendants = unique_descendants_closure child_ask child_tid in
let lockset = ask.f Queries.MustLockset in
let to_contribute = G.singleton tid lockset in
TIDs.iter (contribute_locks man to_contribute) unique_descendants
| _ -> ()

(** compute all descendant threads that may run along with the ego thread at a program point.
for all of them, tid must be an ancestor
@param tid ego thread id
@param ask ask of ego thread at the program point
*)
let get_unique_running_descendants tid (ask : Queries.ask) =
let may_created_tids = ask.f Queries.CreatedThreads in
let may_unique_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 (unique_descendants_closure ask child_tid))
may_unique_created_tids
(TIDs.empty ())
in
let must_joined_tids = ask.f Queries.MustJoinedThreads in
TIDs.diff may_transitively_created_tids must_joined_tids

(** handle unlock of mutex [lock] *)
let unlock man tid possibly_running_tids lock =
let shrink_locksets des_tid =
let old_creation_lockset = G.find tid (man.global des_tid) in
(* Bot - {something} = Bot. This is exactly what we want in this case! *)
let updated_creation_lockset = LIDs.remove lock old_creation_lockset in
let to_contribute = G.singleton tid updated_creation_lockset in
man.sideg des_tid to_contribute
in
TIDs.iter shrink_locksets possibly_running_tids

(** handle unlock of an unknown mutex. Assumes that any mutex could have been unlocked *)
let unknown_unlock man tid possibly_running_tids =
let evaporate_locksets des_tid =
let to_contribute = G.singleton tid (LIDs.empty ()) in
man.sideg des_tid to_contribute
in
TIDs.iter evaporate_locksets possibly_running_tids

let event man e _ =
match e with
| Events.Unlock addr ->
let ask = ask_of_man man in
let tid_lifted = ask.f Queries.CurrentThreadId in
(match tid_lifted with
| `Lifted tid ->
let possibly_running_tids = get_unique_running_descendants tid ask in
let lock_opt = LockDomain.MustLock.of_addr addr in
(match lock_opt with
| Some lock -> unlock man tid possibly_running_tids lock
| None -> unknown_unlock man tid possibly_running_tids)
| _ -> ())
| _ -> ()

module A = struct
(** ego tid * must-lockset * creation-lockset *)
include Printable.Prod3 (TID) (LIDs) (G)

let name () = "creationLockset"

(** checks if [cl1] has a member ([tp1] |-> [ls1]) and [cl2] has a member ([tp2] |-> [ls2])
such that [ls1] and [ls2] are not disjoint and [tp1] != [tp2]
@param cl1 creation-lockset of first thread [t1]
@param cl2 creation-lockset of second thread [t2]
@returns whether [t1] and [t2] must be running mutually exclusive
*)
let both_protected_inter_threaded cl1 cl2 =
let cl2_has_same_lock_other_tid tp1 ls1 =
G.exists (fun tp2 ls2 -> not (LIDs.disjoint ls1 ls2 || TID.equal tp1 tp2)) cl2
in
G.exists cl2_has_same_lock_other_tid cl1

(** checks if [cl1] has a mapping ([tp1] |-> [ls1])
such that [ls1] and [ls2] are not disjoint and [tp1] != [t2]
@param cl1 creation-lockset of thread [t1] at first program point
@param t2 thread id at second program point
@param ls2 lockset at second program point
@returns whether [t1] must be running mutually exclusive with second program point
*)
let one_protected_inter_threaded_other_intra_threaded cl1 t2 ls2 =
G.exists (fun tp1 ls1 -> not (LIDs.disjoint ls1 ls2 || TID.equal tp1 t2)) cl1

let may_race (t1, ls1, cl1) (t2, ls2, cl2) =
not
(both_protected_inter_threaded cl1 cl2
|| one_protected_inter_threaded_other_intra_threaded cl1 t2 ls2
|| one_protected_inter_threaded_other_intra_threaded cl2 t1 ls1)

let should_print (_t, _ls, cl) = not @@ G.is_empty cl
end

let access man _ =
let ask = ask_of_man man in
let tid_lifted = ask.f Queries.CurrentThreadId in
match tid_lifted with
| `Lifted tid ->
let lockset = ask.f Queries.MustLockset in
let creation_lockset = man.global tid in
tid, lockset, creation_lockset
| _ -> ThreadIdDomain.UnknownThread, LIDs.empty (), G.empty ()
end

let _ =
MCP.register_analysis
~dep:[ "threadid"; "mutex"; "threadJoins"; "threadDescendants" ]
(module Spec : MCPSpec)
210 changes: 210 additions & 0 deletions src/analyses/creationLocksetAlternative.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
(** alternative creation lockset analysis
@see https://github.com/goblint/analyzer/pull/1865
*)

open Analyses
module TID = ThreadIdDomain.Thread
module TIDs = ConcDomain.MustThreadSet
module LID = LockDomain.MustLock
module LIDs = LockDomain.MustLockset

(** [creationLocksetAlternative]
collects parent threads, which could protect the ego thread and its descendants,
since the creation must happen with a lock held.
*)
module CreationLocksetAlternative = struct
include IdentityUnitContextsSpec
module D = Lattice.Unit

module V = struct
include TID
include StdV
end

module G = Queries.CLS

let name () = "creationLocksetAlternative"
let startstate _ = D.bot ()
let exitstate _ = D.bot ()

let threadspawn man ~multiple lval f args fman =
let ask = ask_of_man man in
let tid_lifted = ask.f Queries.CurrentThreadId in
let child_ask = ask_of_man fman in
let child_tid_lifted = child_ask.f Queries.CurrentThreadId in
match tid_lifted, child_tid_lifted with
| `Lifted tid, `Lifted child_tid when TID.must_be_ancestor tid child_tid ->
let lockset = ask.f Queries.MustLockset in
let to_contribute = G.singleton tid lockset in
man.sideg child_tid to_contribute
| _ -> ()

let query man (type a) (x : a Queries.t) : a Queries.result =
match x with
| Queries.CreationLocksetAlternative tid -> (man.global tid : G.t)
| _ -> Queries.Result.top x
end

(** [taintedCreationLocksetAlternative]
collects parent threads, which cannot protect the ego thread and its descendants,
since an unlock could happen before joining
*)
module TaintedCreationLocksetAlternative = struct
include IdentityUnitContextsSpec
module D = Lattice.Unit

module V = struct
include TID
include StdV
end

module LockToThreads = MapDomain.MapBot (LID) (TIDs)
module G = MapDomain.MapBot (TID) (LockToThreads)

let name () = "taintedCreationLocksetAlternative"
let startstate _ = D.bot ()
let exitstate _ = D.bot ()

let get_unique_created_children tid (ask : Queries.ask) =
let created_threads = ask.f Queries.CreatedThreads in
TIDs.filter (TID.must_be_ancestor tid) created_threads

(** handle unlock of mutex [lock] *)
let unlock man tid created_tids joined_tids lock =
let contribute_lock child_tid =
let to_contribute = G.singleton tid (LockToThreads.singleton lock joined_tids) in
man.sideg child_tid to_contribute
in
TIDs.iter contribute_lock created_tids

(** handle unlock of an unknown mutex. Assumes that any mutex could have been unlocked *)
let unknown_unlock man tid created_tids joined_tids =
let ask = ask_of_man man in
let contribute_all_locks child_tid =
let all_creation_locksets = ask.f @@ Queries.CreationLocksetAlternative child_tid in
let creation_lockset = CreationLocksetAlternative.G.find tid all_creation_locksets in
let to_contribute_value =
LIDs.fold
(fun lock acc ->
LockToThreads.join acc (LockToThreads.singleton lock joined_tids))
creation_lockset
(LockToThreads.empty ())
in
let to_contribute = G.singleton tid to_contribute_value in
man.sideg child_tid to_contribute
in
TIDs.iter contribute_all_locks created_tids

let event man e _ =
match e with
| Events.Unlock addr ->
let ask = ask_of_man man in
let tid_lifted = ask.f Queries.CurrentThreadId in
(match tid_lifted with
| `Lifted tid ->
let created_tids = get_unique_created_children tid ask in
let joined_tids = ask.f Queries.MustJoinedThreads in
let lock_opt = LockDomain.MustLock.of_addr addr in
(match lock_opt with
| Some lock -> unlock man tid created_tids joined_tids lock
| None -> unknown_unlock man tid created_tids joined_tids)
| _ -> ())
| _ -> ()

module A = struct
(** ego tid * must-lockset * creation-lockset *)
include Printable.Prod3 (TID) (LIDs) (Queries.CLS)

let name () = "creationLockset"

(** checks if [il1] has a member ([tp1] |-> [ls1]) and [il2] has a member ([tp2] |-> [ls2])
such that [ls1] and [ls2] are not disjoint and [tp1] != [tp2]
@param il1 creation-lockset of first thread [t1]
@param il2 creation-lockset of second thread [t2]
@returns whether [t1] and [t2] must be running mutually exclusive
*)
let both_protected_inter_threaded il1 il2 =
let cl2_has_same_lock_other_tid tp1 ls1 =
Queries.CLS.exists
(fun tp2 ls2 -> not (LIDs.disjoint ls1 ls2 || TID.equal tp1 tp2))
il2
in
Queries.CLS.exists cl2_has_same_lock_other_tid il1

(** checks if [il1] has a mapping ([tp1] |-> [ls1])
such that [ls1] and [ls2] are not disjoint and [tp1] != [t2]
@param il1 creation-lockset of thread [t1] at first program point
@param t2 thread id at second program point
@param ls2 lockset at second program point
@returns whether [t1] must be running mutually exclusive with second program point
*)
let one_protected_inter_threaded_other_intra_threaded il1 t2 ls2 =
Queries.CLS.exists
(fun tp1 ls1 -> not (LIDs.disjoint ls1 ls2 || TID.equal tp1 t2))
il1

let may_race (t1, ls1, il1) (t2, ls2, il2) =
not
(both_protected_inter_threaded il1 il2
|| one_protected_inter_threaded_other_intra_threaded il1 t2 ls2
|| one_protected_inter_threaded_other_intra_threaded il2 t1 ls1)

let should_print (_t, _ls, cl) = not @@ Queries.CLS.is_empty cl
end

let access man _ =
let ask = Analyses.ask_of_man man in
let tid_lifted = ask.f Queries.CurrentThreadId in
match tid_lifted with
| `Lifted td ->
let must_ancestors = TID.must_ancestors td in

let compute_cl_transitive () =
let cl_td = ask.f @@ Queries.CreationLocksetAlternative td in
List.fold_left
(fun acc t1 ->
Queries.CLS.join acc (ask.f @@ Queries.CreationLocksetAlternative t1))
cl_td
must_ancestors
in

let compute_tcl_transitive () =
let tcl_td = man.global td in
List.fold_left (fun acc t1 -> G.join acc (man.global t1)) tcl_td must_ancestors
in

let compute_tcl_lockset tcl_transitive t0 =
let tcl_transitive_t0 = G.find t0 tcl_transitive in
LockToThreads.fold
(fun l j acc -> if TIDs.mem td j then acc else LIDs.add l acc)
tcl_transitive_t0
(LIDs.empty ())
in

let compute_il cl_transitive tcl_transitive =
Queries.CLS.fold
(fun t0 l_cl acc ->
let l_tcl = compute_tcl_lockset tcl_transitive t0 in
let l_il = LIDs.diff l_cl l_tcl in
Queries.CLS.add t0 l_il acc)
cl_transitive
(Queries.CLS.empty ())
in

let lockset = ask.f Queries.MustLockset in
let cl_transitive = compute_cl_transitive () in
let tcl_transitive = compute_tcl_transitive () in
let il = compute_il cl_transitive tcl_transitive in
td, lockset, il
| _ -> ThreadIdDomain.UnknownThread, LIDs.empty (), Queries.CLS.empty ()
end

let _ =
MCP.register_analysis
~dep:[ "threadid"; "mutex"; "threadJoins" ]
(module CreationLocksetAlternative : MCPSpec)

let _ =
MCP.register_analysis
~dep:[ "threadid"; "mutex"; "threadJoins"; "creationLocksetAlternative" ]
(module TaintedCreationLocksetAlternative : MCPSpec)
Loading
Loading