Skip to content
Open
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
4f611d5
mustlock history analysis
dabund24 Jan 15, 2026
d4189b4
initial version of descendant lockset analysis
dabund24 Jan 15, 2026
4d5fee2
first descendant lockset racefree tests
dabund24 Jan 15, 2026
2cc4be9
first descendant lockset racing tests
dabund24 Jan 15, 2026
470279f
descendant lockset racing tests with multiple thread creations
dabund24 Jan 18, 2026
1f883f2
creation lockset query
dabund24 Jan 19, 2026
bb16da6
global descendant locksets
dabund24 Jan 19, 2026
8f46eb3
regression tests for global descendant locksets
dabund24 Jan 19, 2026
61cdb71
descendant lockset tests involving multiple mutexes
dabund24 Jan 19, 2026
67a62bd
Merge branch 'master' into descendant-locksets
dabund24 Jan 19, 2026
c8cdf89
fix incorrect indentatoin in regression test
dabund24 Jan 20, 2026
6c68a6c
add missing return statement to regression test
dabund24 Jan 20, 2026
6020267
add missing mutex initializations
dabund24 Jan 20, 2026
5bf536d
remove redundant whitespace in creation lockset analysis
dabund24 Jan 20, 2026
bd388a2
remove unused tid function parameter in `unlock` and `unknown_unlock`…
dabund24 Jan 20, 2026
66ad3f1
modify global domain to use `D` instances as values
dabund24 Jan 20, 2026
d4aec37
Merge branch 'master' into descendant-locksets
dabund24 Jan 20, 2026
743092a
recursive mutex regression tests for descendant locksets
dabund24 Jan 21, 2026
b161fed
Merge branch 'master' into descendant-locksets
dabund24 Jan 21, 2026
3461214
use `MapTop` instead of `MapBot` for inner map of descendant lockset …
dabund24 Jan 25, 2026
717b52f
some documentation in `descendantLockset` analysis
dabund24 Jan 25, 2026
ee18c9a
improve printing of `descendantLockset` analysis
dabund24 Jan 25, 2026
9f5b264
documentation for `mustlockHistory` analysis
dabund24 Jan 25, 2026
83d6f5a
remove unused dependencies from mustlock history analysis
dabund24 Jan 25, 2026
6785efa
Merge branch 'master' into descendant-locksets
dabund24 Jan 25, 2026
a71d29e
refactor functions related to thread descendants
dabund24 Jan 26, 2026
be913ba
locally abstract type for `happens_before` function in `descendantLoc…
dabund24 Jan 26, 2026
624254b
also print mustlock history in `A` of descendant lockset analysis
dabund24 Jan 28, 2026
30d7daf
remove side effects analysis for descendant locksets
dabund24 Feb 2, 2026
9b40093
skip tests involving global descendant lockset analysis
dabund24 Feb 2, 2026
ba0cac3
Merge branch 'master' into descendant-locksets
dabund24 Feb 2, 2026
9e8399e
undo removal of descendant lockset analysis
dabund24 Feb 19, 2026
6827d4d
undo skipping global descendant lockset regression tests
dabund24 Feb 19, 2026
ac2b66f
also use MapBot for global domain of descendant lockset analysis
dabund24 Feb 19, 2026
b0e6f4b
align definition of transfer functions of descendant lockset analysis…
dabund24 Feb 19, 2026
6dd1f3c
use sticky set intersection in global descendant lockset analysis
dabund24 Feb 19, 2026
596cb68
add global descendant lockset regression tests with multiple creates
dabund24 Feb 19, 2026
7e4f3d8
threadenter of descendant lockset analysis
dabund24 Feb 20, 2026
f3ba129
Merge branch 'master' into descendant-locksets
dabund24 Feb 23, 2026
2ab8e7f
Merge remote-tracking branch 'origin/master' into descendant-locksets
dabund24 May 5, 2026
f855eda
remove redundant quotation marks in descendant lockset test params
dabund24 May 5, 2026
3d401db
svcomp nondet initializers for descendant lockset tests
dabund24 May 5, 2026
0f52534
TIDV module
dabund24 May 5, 2026
b395b79
add dl test sometimes creation without lock
dabund24 May 5, 2026
e69cfd8
reference inter-threaded lockset bachelor's thesis
dabund24 May 5, 2026
c6a43d7
remove unnecessary parentheses in descendant lockset analysis
dabund24 May 5, 2026
e05ae7b
svcomp nondet initializers for creation lockset tests
dabund24 May 5, 2026
b0adaca
Indentation in descendant locksets analysis
dabund24 May 6, 2026
dfb2eba
fix inter-threaded lockset bachelor's thesis reference
dabund24 May 6, 2026
5ec0f12
enable svcomp functions for tests using nondet
dabund24 May 6, 2026
bc552ed
add missing RACE! annotations for descendant lockset tests
dabund24 May 6, 2026
31f5f3f
fix comments in global descendant lockset test
dabund24 May 6, 2026
76c1aa8
improve descendant lockset test case where same thread is created mul…
dabund24 May 6, 2026
425d0d4
remove unused query for current tid in descendant lockset analysis
dabund24 May 6, 2026
ec2b4f5
move query for must-lock history in descendant lockset analysis into …
dabund24 May 6, 2026
6d501eb
add missing line break before new function in thread descendants anal…
dabund24 May 6, 2026
ab3b3c6
indentation in descendant lockset test
dabund24 May 8, 2026
81d3ac9
remove redundant pthread declaration in descendant lockset test
dabund24 May 8, 2026
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
53 changes: 14 additions & 39 deletions src/analyses/creationLockset.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
constructs edges on the graph over all threads, where the edges are labelled with must-locksets:
(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

@see https://github.com/goblint/analyzer/pull/1865
@see Bachelor's Thesis "Leveraging th Potential of Inter-Threaded Locksets in Abstract Interpretation". Available upon request.
Comment thread
dabund24 marked this conversation as resolved.
Outdated
*)

open Analyses
Expand All @@ -14,13 +14,8 @@ module Lockset = LockDomain.MustLockset
module Spec = struct
include IdentityUnitContextsSpec
module D = Lattice.Unit

module V = struct
include TID
include StdV
end

module G = MapDomain.MapBot (TID) (Lockset)
module V = TIDV
module G = Queries.CL

let name () = "creationLockset"
let startstate _ = D.bot ()
Expand All @@ -33,46 +28,19 @@ module Spec = struct
*)
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 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

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
let must_ancestor_descendants =
ThreadDescendants.must_ancestor_descendants_closure fman child_tid
in
let lockset = man.ask Queries.MustLockset in
let to_contribute = G.singleton tid lockset in
TIDs.iter (contribute_locks man to_contribute) must_ancestor_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 man man of ego thread at the program point
@param tid ego thread id
*)
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_mustset 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 =
Expand All @@ -98,7 +66,9 @@ module Spec = struct
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 possibly_running_tids =
ThreadDescendants.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 tid possibly_running_tids lock
Expand Down Expand Up @@ -154,6 +124,11 @@ module Spec = struct
let creation_lockset = man.global tid in
tid, lockset, creation_lockset
| _ -> ThreadIdDomain.UnknownThread, Lockset.empty (), G.empty ()

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

let _ =
Expand Down
165 changes: 165 additions & 0 deletions src/analyses/descendantLockset.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
(** 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 Bachelor's Thesis "Leveraging th Potential of Inter-Threaded Locksets in Abstract Interpretation". Available upon request.
Comment thread
dabund24 marked this conversation as resolved.
Outdated
*)
Comment thread
dabund24 marked this conversation as resolved.

open Analyses
module TID = ThreadIdDomain.Thread
module TIDs = ConcDomain.ThreadSet
module Lockset = LockDomain.MustLockset

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,
Comment thread
michael-schwarz marked this conversation as resolved.
because [t_d] was created in [t_0] with the lockset being a superset of L.
*)
module G = MapDomain.MapBot (TID) (D)

module V = TIDV

let name () = "descendantLockset"
let startstate _ = D.empty ()
let exitstate _ = D.empty ()

let threadenter man ~multiple lval f args = [ D.empty () ]

let threadspawn_contribute_globals man tid must_ancestor_descendants =
let descendant_lockset = man.local in

(* intersect locksets, but return bot if any arg is bot *)
let lockset_inter_sticky_bot = function
| `Top, _ | _, `Top -> Lockset.bot ()
| ls1, ls2 -> Lockset.inter ls1 ls2
Comment on lines +42 to +45
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Lockset.inter somehow incorrect or why is this necessary?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really interesting point. What I wanted to achieve here is explicitly checking which mutexes are included in both locksets (where besides $\emptyset$, $\bot$ also means that no mutex is included). So in theory, this expresses what we actually want here.

In practice, though, this will never happen (at least with how the analyses work right now), as in both cases, $\bot$ means that a thread has never been created at all from some other thread (meaning $\mathcal{CL}\ t_1\ t_{\mathrm{ego}} = \bot\iff \mathcal{DL}\ t_1 =\bot$). So Lockset.inter should also work here, even if a little by chance.

Not sure how to proceed here. If you strongly lean towards using Lockset.inter, I can change this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are must mutexes, right? So

`Top

which is the same as bot () is actually the full set of mutexes, not $\emptyset$.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I phrased this in a somewhat sloppy way. In my analyses, $\bot$ actually does not represent "all mutexes", but "no mutexes" is not 100% spot on either. Rather, it means that some "may/ $\exists$ -condition" is not satisfied, which is the bare minimum for any mutex to be included in the lockset.
For instance, in the case of the creation lockset, a mutex cannot possibly protect a thread $t_1$ from another thread $t_0$ if $t_0$ never creates $t_1$ (transitively). However, we cannot encode this with $\emptyset$, because we want to be able to "eliminate" this state by joining it with something. In must-locksets this is not possible. Using $\bot$ for this works perfectly, though

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only issue I see these two representations to possibly clash is when reading out the must-lockset query. This would fortunately only be a precision problem, however, I admit that I have not given this a lot of thought

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for the implementation this is fine for now, for the writeup we may have to think how to separate those things cleanly.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I guess it is a bit similar to #1977. I think it would be good to have some of this explanation as a code comment.

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_sticky_bot (l_cl, l_dl) in
D.add t_l l_inter acc)
descendant_lockset
(D.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 ->
Comment thread
michael-schwarz marked this conversation as resolved.
let must_ancestor_descendants =
ThreadDescendants.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 unlock man lock =
D.map (Lockset.remove lock) man.local

let unknown_unlock man =
D.map (fun _ -> Lockset.empty ()) man.local

let event man e _ =
match e with
| Events.Unlock addr ->
let tid_lifted = man.ask Queries.CurrentThreadId in
let lock_opt = LockDomain.MustLock.of_addr addr in
(match tid_lifted, lock_opt with
| `Lifted tid, Some lock -> unlock man lock
| `Lifted tid, None -> unknown_unlock man
Comment thread
dabund24 marked this conversation as resolved.
Outdated
| _ -> man.local)
| _ -> man.local

module A = struct
module DlLhProd = Printable.Prod3 (D) (G) (Queries.LH)

(** ego tid * (local descendant lockset * global descendant lockset * lock history) *)
include Printable.Prod (TID) (DlLhProd)

(** checks if program point 1 must happen before program point 2
@param (t1,dl1) thread id and descendant lockset of program point 1
@param (t2,lh2) thread id and mustlock history of program point 2
@param M module of [dl1]
*)
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

(** checks if the entire execution of a thread must happen before a program point
@param dlg1 glabal descendant lockset of the thread
@param (t2,lh2) thread id and mustlock history of the program point
*)
let happens_before_global dlg1 (t2, lh2) =
G.exists (fun t dl_map -> happens_before (t, dl_map) (t2, lh2)) dlg1

let may_race (t1, (dl1, dlg1, lh1)) (t2, (dl2, dlg2, lh2)) =
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))

(* ego tid is already printed elsewhere *)
let pretty () (_, dl_dlg_lh) = DlLhProd.pretty () dl_dlg_lh
let show (_, dl_dlg_lh) = DlLhProd.show dl_dlg_lh
let to_yojson (_, dl_dlg_lh) = DlLhProd.to_yojson dl_dlg_lh
let printXml f (_, dl_dlg_lh) = DlLhProd.printXml f dl_dlg_lh

let should_print (_, (dl, dlg, lh)) =
let ls_not_empty _ ls = not @@ Lockset.is_empty ls in
D.exists ls_not_empty dl
|| G.exists (fun _ -> D.exists ls_not_empty) dlg
|| Queries.LH.exists (fun l tids -> not @@ TIDs.is_empty tids) lh
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, (man.local, man.global tid, lh)
Comment thread
dabund24 marked this conversation as resolved.
Outdated
| _ -> ThreadIdDomain.UnknownThread, (D.empty (), G.empty (), Queries.LH.empty ())
end

let _ =
MCP.register_analysis
~dep:[ "threadid"; "mutex"; "threadJoins"; "threadDescendants"; "mustlockHistory" ]
Comment thread
michael-schwarz marked this conversation as resolved.
(module Spec : MCPSpec)
47 changes: 47 additions & 0 deletions src/analyses/mustlockHistory.ml
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
Comment thread
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)
36 changes: 30 additions & 6 deletions src/analyses/threadDescendants.ml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,40 @@

open Analyses
module TID = ThreadIdDomain.Thread
module TIDs = ConcDomain.ThreadSet

(** 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
(** compute all descendant threads that may run along with the ego thread at a program point.
Comment thread
dabund24 marked this conversation as resolved.
for all of them, tid must be an ancestor
@param man man of ego thread at the program point
@param tid ego thread id
*)
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_mustset may_transitively_created_tids must_joined_tids

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

module V = struct
include TID
include StdV
end

module V = TIDV
module G = ConcDomain.ThreadSet

let name () = "threadDescendants"
Expand Down
6 changes: 1 addition & 5 deletions src/analyses/threadJoins.ml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,7 @@ struct
module D = Lattice.Prod(MustTIDsWithBot)(CleanExit)
include Analyses.ValueContexts(D)
module G = D
module V =
struct
include TID
include StdV
end
module V = TIDV

(* transfer functions *)
let threadreturn man =
Expand Down
Loading
Loading