HB-relationship involving thread creations while mutexes are held#1913
HB-relationship involving thread creations while mutexes are held#1913dabund24 wants to merge 58 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR implements the second part of happens-before (HB) relationship analysis for thread creations while mutexes are held. It introduces two new analyses (MustlockHistory and DescendantLockset) that work together to detect race conditions by establishing happens-before relationships between thread operations based on mutex locking patterns.
Changes:
- Added
MustlockHistoryanalysis to track which threads have locked specific mutexes - Added
DescendantLocksetanalysis to compute descendant locksets and determine happens-before relationships - Extended
CreationLocksetanalysis with query support for integration with the new analyses - Added 20 comprehensive test cases covering both race-free and racing scenarios
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
src/analyses/mustlockHistory.ml |
New analysis tracking mutex lock history per thread |
src/analyses/descendantLockset.ml |
New analysis computing descendant locksets and HB relationships |
src/analyses/creationLockset.ml |
Added CreationLockset query support |
src/domains/queries.ml |
Added CreationLockset and MustlockHistory query types with supporting domains |
src/goblint_lib.ml |
Exported the two new analysis modules |
tests/regression/53-races-mhp/40-45-*.c |
Race-free test cases validating correct HB detection |
tests/regression/53-races-mhp/50-59-*.c |
Racing test cases validating race detection |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… of descendant lockset analysis
|
Random thought: What do your analyses do for recursive mutexes? Are they sound in these cases? |
|
Thanks for bringing this up, those would never have crossed my mind. I think the analyses remain sound, but get less precise. The only relevant thing changing here coming to my mind is the fact that after an unlock, we can't assume anymore that the mutex is now unlocked. As unlock statements have been places in our analyses, where we assume things to just break, but not start/keep working, this wouldn't be an issue. |
👍 Could you add tests here and maybe also include some tests for your first analysis (potentially in a separate PR)? |
|
I re-added the |
michael-schwarz
left a comment
There was a problem hiding this comment.
Sorry for the stall here. I think we should now try to get this merged so it evolves with the rest of the system. Can you merge master into this (or rebase, whatever you prefer) and address the comments?
Then it should be good to merge!
Co-authored-by: Michael Schwarz <michael.schwarz93@gmail.com>
| (* 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 |
There was a problem hiding this comment.
Is Lockset.inter somehow incorrect or why is this necessary?
There was a problem hiding this comment.
This is a really interesting point. What I wanted to achieve here is explicitly checking which mutexes are included in both locksets (where besides
In practice, though, this will never happen (at least with how the analyses work right now), as in both cases, 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.
There was a problem hiding this comment.
Those are must mutexes, right? So
`Topwhich is the same as bot () is actually the full set of mutexes, not
There was a problem hiding this comment.
I phrased this in a somewhat sloppy way. In my analyses,
For instance, in the case of the creation lockset, a mutex cannot possibly protect a thread
There was a problem hiding this comment.
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
There was a problem hiding this comment.
I think for the implementation this is fine for now, for the writeup we may have to think how to separate those things cleanly.
…tiple times with different locksets
…only relevant match case
second part of #1805. The first half was implemented in #1865.
closes #1805.
Summary
Simplest case: After creating$t_1$ in $t_0$ with mutex $l$ held, succeeding statements until maybe unlocking in $t_0$ must happen before everything after definitely locking $l$ in $t_1$ .
generalizations:
Examples
In the following examples,
Amust happen beforeB.Simple example
graph TB; subgraph t1; E["lock(l);"]-->F; F["unlock(l);"]-->G; G((B)) end; subgraph t0; A["lock(l);"]-->B; B["create(t1);"]-->C; C((A))-->D; D["unlock(l);"]; end; B-.->EBin a descendant ofgraph TB; subgraph t2; H((B)) end; subgraph t1; E["lock(l);"]-->F; F["create(t2);"] end; subgraph t0; A["lock(l);"]-->B; B["create(t1);"]-->C; C((A))-->D; D["unlock(l);"]; end; B-.->E F-.->HAin a descendant ofgraph TB; subgraph t1; E["lock(l);"]-->I; I["unlock(l);"]-->F; F((B)); end; subgraph t2; H((A)) end; subgraph t0; A["lock(l);"]-->B; B["create(t1);"]-->C; C["create(t2);"]-->D; D["join(t2);"]-->G; G["unlock(l);"] end; B-.->E C-.->H H-.->DHere, it is important that no unlock happens in$t_0$ before $t_2$ is joined into $t_0$ , which was computed in #1865.
Dependency Analyses
From these analyses, we compute:
create(t)all threads transitively created, for whichAnalyses
Descendant Locksets$\mathcal{DL}$
flow-sensitive
Domain:$T\to 2^L$
MapBotThere must have existed at least one$t_c$ $t_A$ with $t_B\in c^*\ t_c$ .
create()statement inFor all of those$t_c$ $\mathit{l}\in\mathcal{L}$ .
create()statements,We must not have encountered an$l$
unlock()statement after having detected the thread creation.Transfer functions
Mustlock History$\mathcal{LH}$
MapTopTransfer functions
Global descendant lockset$\mathcal{DL}_g\ t$
VMapBotContributions
We only contribute at$t_c$ $t_d\in t^*\ t_c$ :
$$DL_{t_d}:=\set{t\mapsto (\mathcal{DL}\ t)\cap (\mathcal{CL}\ t_d\ t_{\mathrm{ego}})\mid t\in T}$$
$$\mathcal{DL}_ g\ t_ d\sqsupseteq \set{t_ {\mathrm{ego}} \mapsto DL_ {t_d}}$$
create()statements for allHappened-Before rules
Statement$\mathcal{LH}_ 2, t_2$ must happen after $\mathcal{DL}_ 1, \mathcal{LH}_ 1, t_1$ , if:
s2withs1with