HB-relationship involving thread creations while mutexes are held - #1913
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)? |
Co-authored-by: Michael Schwarz <michael.schwarz93@gmail.com>
…tiple times with different locksets
…only relevant match case
CHANGES: * Add new may-happen-in-parallel analyses (goblint/analyzer#1805, goblint/analyzer#1865, goblint/analyzer#1913, goblint/analyzer#1928). * Add Open Verification Dashboard checks output (goblint/analyzer#1838, goblint/analyzer#1929). * Add negative bitwise shift warnings (goblint/analyzer#1637, goblint/analyzer#1989). * Add missing function declaration warnings (goblint/analyzer#1911). * Improve overflow warnings (goblint/analyzer#1894, goblint/analyzer#1895, goblint/analyzer#1896, goblint/analyzer#1905). * Fix spurious overflow checks (goblint/analyzer#1767, goblint/analyzer#1909, goblint/analyzer#1910, goblint/analyzer#1932, goblint/analyzer#2022). * Fix missing overflow and out-of-bounds checks (goblint/analyzer#1935, goblint/analyzer#2017, goblint/analyzer#2029). * Optimize base analysis domain using Patricia trees (goblint/analyzer#2002, goblint/analyzer#2015). * Optimize field offset calculations (goblint/analyzer#1964, goblint/analyzer#1973, goblint/analyzer#1974). * Optimize non-incremental top-down solver (goblint/analyzer#1566, goblint/analyzer#1972). * Add OCaml 5.5 support (goblint/analyzer#2006, goblint/analyzer#2010).
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