threadcheck 0.0.1__tar.gz → 0.0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/.github/workflows/release.yml +48 -6
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/.github/workflows/test.yml +3 -2
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/.gitignore +6 -0
- threadcheck-0.0.1.2/PKG-INFO +372 -0
- threadcheck-0.0.1.2/PLAN.md +53 -0
- threadcheck-0.0.1.2/README.md +349 -0
- threadcheck-0.0.1.2/README_CN.md +356 -0
- threadcheck-0.0.1.2/demo/race_example.py +71 -0
- threadcheck-0.0.1.2/demo/run_demo.py +87 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/pyproject.toml +5 -0
- threadcheck-0.0.1.2/release_body.md +10 -0
- threadcheck-0.0.1.2/scripts/tag_wheel.py +61 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/__init__.py +1 -0
- threadcheck-0.0.1.2/src/threadcheck/_tid.py +13 -0
- threadcheck-0.0.1.2/src/threadcheck/_tid_linux.py +9 -0
- threadcheck-0.0.1.2/src/threadcheck/_tid_macos.py +9 -0
- threadcheck-0.0.1.2/src/threadcheck/_tid_win.py +5 -0
- threadcheck-0.0.1.2/src/threadcheck/_version.py +1 -0
- threadcheck-0.0.1.2/src/threadcheck/cli.py +241 -0
- threadcheck-0.0.1.2/src/threadcheck/compat/__init__.py +4 -0
- threadcheck-0.0.1.2/src/threadcheck/compat/checker.py +95 -0
- threadcheck-0.0.1.2/src/threadcheck/compat/models.py +40 -0
- threadcheck-0.0.1.2/src/threadcheck/config.py +104 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/dynamic/__main__.py +19 -3
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/dynamic/hook.py +0 -2
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/dynamic/tracker.py +21 -26
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/dynamic/transform.py +110 -29
- threadcheck-0.0.1.2/src/threadcheck/reporting/__init__.py +12 -0
- threadcheck-0.0.1.2/src/threadcheck/reporting/formatter.py +251 -0
- threadcheck-0.0.1.2/src/threadcheck/reporting/html.py +168 -0
- threadcheck-0.0.1.2/src/threadcheck/reporting/types.py +8 -0
- threadcheck-0.0.1.2/src/threadcheck/static/analyzer.py +198 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/static/lock_tracker.py +25 -2
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/static/visitors.py +9 -1
- threadcheck-0.0.1.2/tests/fixtures/closure_lock_safe.py +21 -0
- threadcheck-0.0.1.2/tests/fixtures/cross_module_main.py +7 -0
- threadcheck-0.0.1.2/tests/fixtures/cross_module_worker.py +7 -0
- threadcheck-0.0.1.2/tests/fixtures/dynamic_read_race.py +21 -0
- threadcheck-0.0.1.2/tests/fixtures/dynamic_subscript_race.py +26 -0
- threadcheck-0.0.1.2/tests/fixtures/futures_race.py +16 -0
- threadcheck-0.0.1.2/tests/fixtures/inline_ignore.py +32 -0
- threadcheck-0.0.1.2/tests/fixtures/manual_lock.py +20 -0
- threadcheck-0.0.1.2/tests/fixtures/name_main_guard.py +17 -0
- threadcheck-0.0.1.2/tests/fixtures/nested_locks.py +18 -0
- threadcheck-0.0.1.2/tests/test_compat.py +173 -0
- threadcheck-0.0.1.2/tests/test_config.py +108 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/test_dynamic_detector.py +73 -0
- threadcheck-0.0.1.2/tests/test_formatter.py +191 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/test_pytest_plugin.py +2 -1
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/test_static_analyzer.py +65 -0
- threadcheck-0.0.1/PKG-INFO +0 -248
- threadcheck-0.0.1/PLAN.md +0 -292
- threadcheck-0.0.1/README.md +0 -225
- threadcheck-0.0.1/README_CN.md +0 -230
- threadcheck-0.0.1/demo/race_example.py +0 -21
- threadcheck-0.0.1/demo/run_demo.py +0 -44
- threadcheck-0.0.1/release_body.md +0 -16
- threadcheck-0.0.1/src/threadcheck/_version.py +0 -1
- threadcheck-0.0.1/src/threadcheck/cli.py +0 -89
- threadcheck-0.0.1/src/threadcheck/reporting/formatter.py +0 -33
- threadcheck-0.0.1/src/threadcheck/reporting/types.py +0 -3
- threadcheck-0.0.1/src/threadcheck/static/__init__.py +0 -0
- threadcheck-0.0.1/src/threadcheck/static/analyzer.py +0 -104
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/.github/workflows/diagnose_hook.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/.github/workflows/diagnose_pytest.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/LICENSE +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/__main__.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/dynamic/__init__.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/dynamic/clock.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/pytest_plugin.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/reporting/sarif.py +0 -0
- {threadcheck-0.0.1/src/threadcheck/reporting → threadcheck-0.0.1.2/src/threadcheck/static}/__init__.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/static/models.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/conftest.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/class_attribute_safe.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/class_race.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/class_safe.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/dynamic_race.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/dynamic_safe.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/no_race_simple.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/nonlocal_race.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/safe_with_lock.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/shared_list.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/simple_global.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/thread_subclass_race.py +0 -0
- {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/test_sarif.py +0 -0
|
@@ -25,9 +25,49 @@ jobs:
|
|
|
25
25
|
- run: pip install -e ".[test]"
|
|
26
26
|
- run: python -m pytest tests/ -v
|
|
27
27
|
|
|
28
|
+
build_wheels:
|
|
29
|
+
name: Build wheel for ${{ matrix.plat }}
|
|
30
|
+
needs: test
|
|
31
|
+
runs-on: ${{ matrix.os }}
|
|
32
|
+
strategy:
|
|
33
|
+
matrix:
|
|
34
|
+
include:
|
|
35
|
+
- os: ubuntu-latest
|
|
36
|
+
plat: manylinux_2_28_x86_64
|
|
37
|
+
tid_src: _tid_linux.py
|
|
38
|
+
- os: windows-latest
|
|
39
|
+
plat: win_amd64
|
|
40
|
+
tid_src: _tid_win.py
|
|
41
|
+
- os: macos-latest
|
|
42
|
+
plat: macosx_11_0_arm64
|
|
43
|
+
tid_src: _tid_macos.py
|
|
44
|
+
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v4
|
|
47
|
+
|
|
48
|
+
- uses: actions/setup-python@v5
|
|
49
|
+
with:
|
|
50
|
+
python-version: "3.12"
|
|
51
|
+
|
|
52
|
+
- name: Copy platform tid module
|
|
53
|
+
run: cp src/threadcheck/${{ matrix.tid_src }} src/threadcheck/_tid.py
|
|
54
|
+
|
|
55
|
+
- name: Build wheel
|
|
56
|
+
run: |
|
|
57
|
+
pip install build
|
|
58
|
+
python -m build --wheel
|
|
59
|
+
|
|
60
|
+
- name: Tag wheel with platform tag
|
|
61
|
+
run: python scripts/tag_wheel.py dist/ ${{ matrix.plat }}
|
|
62
|
+
|
|
63
|
+
- uses: actions/upload-artifact@v4
|
|
64
|
+
with:
|
|
65
|
+
name: wheel-${{ matrix.plat }}
|
|
66
|
+
path: dist/*.whl
|
|
67
|
+
|
|
28
68
|
release:
|
|
29
69
|
name: Create Release & Publish to PyPI
|
|
30
|
-
needs:
|
|
70
|
+
needs: build_wheels
|
|
31
71
|
runs-on: ubuntu-latest
|
|
32
72
|
|
|
33
73
|
steps:
|
|
@@ -115,15 +155,17 @@ jobs:
|
|
|
115
155
|
**Repository Size:** ${{ steps.repo_size.outputs.size }} KB
|
|
116
156
|
EOF
|
|
117
157
|
|
|
118
|
-
- name:
|
|
119
|
-
uses: actions/
|
|
158
|
+
- name: Download all wheels
|
|
159
|
+
uses: actions/download-artifact@v4
|
|
120
160
|
with:
|
|
121
|
-
|
|
161
|
+
pattern: wheel-*
|
|
162
|
+
path: dist/
|
|
163
|
+
merge-multiple: true
|
|
122
164
|
|
|
123
|
-
- name: Build
|
|
165
|
+
- name: Build source distribution
|
|
124
166
|
run: |
|
|
125
167
|
pip install build
|
|
126
|
-
python -m build
|
|
168
|
+
python -m build --sdist
|
|
127
169
|
|
|
128
170
|
- name: Create GitHub Release
|
|
129
171
|
uses: softprops/action-gh-release@v2
|
|
@@ -8,10 +8,11 @@ on:
|
|
|
8
8
|
|
|
9
9
|
jobs:
|
|
10
10
|
test:
|
|
11
|
-
name: Test
|
|
12
|
-
runs-on:
|
|
11
|
+
name: Test ${{ matrix.os }} Python ${{ matrix.python-version }}
|
|
12
|
+
runs-on: ${{ matrix.os }}
|
|
13
13
|
strategy:
|
|
14
14
|
matrix:
|
|
15
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
15
16
|
python-version: ["3.12", "3.13", "3.14"]
|
|
16
17
|
|
|
17
18
|
steps:
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: threadcheck
|
|
3
|
+
Version: 0.0.1.2
|
|
4
|
+
Summary: Data Race Detector for Free-Threading Python
|
|
5
|
+
Project-URL: Homepage, https://github.com/ChidcGithub/Threadcheck
|
|
6
|
+
Project-URL: Source, https://github.com/ChidcGithub/Threadcheck
|
|
7
|
+
Project-URL: BugTracker, https://github.com/ChidcGithub/Threadcheck/issues
|
|
8
|
+
Author: threadcheck contributors
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
18
|
+
Classifier: Topic :: Software Development :: Testing
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Provides-Extra: test
|
|
21
|
+
Requires-Dist: pytest; extra == 'test'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# Threadcheck
|
|
25
|
+
|
|
26
|
+
[](https://www.python.org)
|
|
27
|
+
[](LICENSE)
|
|
28
|
+
[]()
|
|
29
|
+
[](https://github.com/ChidcGithub/Threadcheck/actions/workflows/test.yml)
|
|
30
|
+
[](https://github.com/astral-sh/ruff)
|
|
31
|
+
|
|
32
|
+
Python data race detector for the free-threading (no-GIL) era. Detects concurrent access to shared mutable state in multi-threaded Python programs through static analysis and runtime instrumentation.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Problem
|
|
37
|
+
|
|
38
|
+
Python 3.14 (2026) introduces free-threading, removing the Global Interpreter Lock (GIL). This enables true parallel execution of multi-threaded code, but the ecosystem lacks debugging tools for concurrency bugs. Go has `-race`, C++ has ThreadSanitizer, Java has SpotBugs. Python has nothing comparable without recompiling the interpreter with Clang and TSan.
|
|
39
|
+
|
|
40
|
+
threadcheck is a pure-Python race detector that installs with `pip` and works out of the box across Linux, Windows, and macOS.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
- **Static analysis** -- scans AST for shared mutable state (`global`, `nonlocal`, class attributes, module-level lists/dicts) and missing lock protection
|
|
47
|
+
- **Runtime detection** -- instruments code via AST transformation at import time; tracks memory accesses with vector clocks and detects happens-before violations (read-write and write-write races)
|
|
48
|
+
- **Lock-aware suppression** -- understands `threading.Lock`/`RLock` and `with`-based synchronization; supports nested locks (`with lock1, lock2:`) with automatic per-lock vector clock tracking
|
|
49
|
+
- **Cross-module analysis** -- two-pass scan collects `Thread(target=...)` and `executor.submit/map` targets across all files in a directory
|
|
50
|
+
- **Confidence scoring** -- each warning tagged HIGH / MEDIUM / LOW based on thread context and lock coverage
|
|
51
|
+
- **Configuration** -- `.threadcheckignore` file (gitignore-style patterns + `file:line` suppression) and `[tool.threadcheck]` section in `pyproject.toml`
|
|
52
|
+
- **Multiple output formats** -- terminal (grouped by file, colorized), JSON, SARIF v2.1.0, and self-contained HTML report
|
|
53
|
+
- **pytest plugin** -- automatic race detection during test execution via `--threadcheck` flag
|
|
54
|
+
- **Free-threading compatibility checker** -- `threadcheck compat` scans installed packages for C extensions and checks FT ABI tags
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install threadcheck
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Requires Python 3.12+. Python 3.14+ is recommended for free-threading features.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Quick Start
|
|
69
|
+
|
|
70
|
+
### Static Analysis
|
|
71
|
+
|
|
72
|
+
Scan a file or directory for potential race conditions without running any code:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
threadcheck scan my_project/
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Output (grouped by file, with severity icons and per-file summary):
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
[1/2] my_project/counter.py
|
|
82
|
+
--------------------------------
|
|
83
|
+
[!] HIGH [unsafe_global] line 8:8
|
|
84
|
+
Global variable `counter` modified without lock
|
|
85
|
+
suggestion: Use `threading.Lock()` to protect `counter`
|
|
86
|
+
[i] LOW [thread_usage] line 10:11
|
|
87
|
+
Thread creation detected (target=increment)
|
|
88
|
+
|
|
89
|
+
[2/2] my_project/worker.py
|
|
90
|
+
--------------------------------
|
|
91
|
+
[!] HIGH [shared_mutable] line 15:8
|
|
92
|
+
Module-level mutable object `results.append()` called from multiple threads
|
|
93
|
+
|
|
94
|
+
Total: 2 issue(s) in 2 file(s) (0 error(s), 2 warning(s), 0 info(s))
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Runtime Detection
|
|
98
|
+
|
|
99
|
+
Execute a script with instrumentation to detect actual data races:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
threadcheck run my_script.py
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Output for a racing script:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
Data races detected:
|
|
109
|
+
|
|
110
|
+
[!] `counter`
|
|
111
|
+
|-- Thread-28928 (write) at my_script.py:8
|
|
112
|
+
|-- Thread-9888 (write) at my_script.py:8
|
|
113
|
+
\-- No happens-before relationship between accesses
|
|
114
|
+
(10000 overlapping accesses)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
A script protected with locks reports:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
No data races detected
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Free-threading Compatibility Check
|
|
124
|
+
|
|
125
|
+
Check whether your project's dependencies support free-threading:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
threadcheck compat
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Output:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
threadcheck compat - Free-threading compatibility check
|
|
135
|
+
Python 3.13.10
|
|
136
|
+
|
|
137
|
+
[OK] numpy C extension has free-threading tag (cp313t-)
|
|
138
|
+
[??] torch C extension without free-threading tag
|
|
139
|
+
[OK] pytest pure Python, no C extensions
|
|
140
|
+
|
|
141
|
+
Total: 3 package(s) - 2 compatible, 1 need verification, 0 not installed
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### HTML Report
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
threadcheck scan my_project/ -o report.html
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Generates a self-contained HTML report with dark/light theme, sortable table, and summary cards.
|
|
151
|
+
|
|
152
|
+
### Quiet / Verbose Modes
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
threadcheck scan my_project/ -q # one-line summary only
|
|
156
|
+
threadcheck scan my_project/ -v # include source code snippets
|
|
157
|
+
threadcheck scan my_project/ # default grouped output
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Configuration
|
|
163
|
+
|
|
164
|
+
### `.threadcheckignore`
|
|
165
|
+
|
|
166
|
+
Create a `.threadcheckignore` file in your project root (gitignore-style patterns):
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
# Ignore generated files
|
|
170
|
+
generated/*.py
|
|
171
|
+
build/*.py
|
|
172
|
+
|
|
173
|
+
# Ignore specific lines in a specific file
|
|
174
|
+
src/legacy.py:42 # suppress line 42
|
|
175
|
+
src/legacy.py:50-60 # suppress lines 50-60
|
|
176
|
+
|
|
177
|
+
# Negation (do not ignore)
|
|
178
|
+
*.py
|
|
179
|
+
!important.py
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### `pyproject.toml`
|
|
183
|
+
|
|
184
|
+
```toml
|
|
185
|
+
[tool.threadcheck]
|
|
186
|
+
ignore = [
|
|
187
|
+
"build/*",
|
|
188
|
+
"generated/*.py",
|
|
189
|
+
]
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Both sources are merged automatically.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Output Formats
|
|
197
|
+
|
|
198
|
+
| Flag | Format | Use Case |
|
|
199
|
+
|---|---|---|
|
|
200
|
+
| *(default)* | Terminal (colorized, grouped) | Interactive use |
|
|
201
|
+
| `-q` | One-line summary | Quick status |
|
|
202
|
+
| `-v` | Terminal + source snippets | Debugging |
|
|
203
|
+
| `--json` | JSON | CI pipelines |
|
|
204
|
+
| `--sarif` | SARIF v2.1.0 | GitHub CodeQL integration |
|
|
205
|
+
| `-o report.html` | Self-contained HTML | Team reports |
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Commands
|
|
210
|
+
|
|
211
|
+
| Command | Description |
|
|
212
|
+
|---|---|
|
|
213
|
+
| `scan <path>` | Static race analysis of file or directory |
|
|
214
|
+
| `run <script>` | Execute script with runtime race detection |
|
|
215
|
+
| `compat [path]` | Check free-threading compatibility of dependencies |
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Library Usage
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from threadcheck import analyze_file, analyze_path
|
|
223
|
+
|
|
224
|
+
warnings = analyze_file("my_module.py")
|
|
225
|
+
warnings = analyze_path("src/")
|
|
226
|
+
|
|
227
|
+
for w in warnings:
|
|
228
|
+
print(f"{w.file}:{w.line} [{w.confidence.value}] {w.message}")
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
from threadcheck.dynamic.tracker import ThreadCheckTracker
|
|
233
|
+
from threadcheck.dynamic.transform import transform_and_compile
|
|
234
|
+
|
|
235
|
+
code = transform_and_compile(source, "script.py")
|
|
236
|
+
ThreadCheckTracker.start()
|
|
237
|
+
exec(code, {"_threadcheck_tracker": ThreadCheckTracker})
|
|
238
|
+
ThreadCheckTracker.stop()
|
|
239
|
+
|
|
240
|
+
print(ThreadCheckTracker.format_races())
|
|
241
|
+
ThreadCheckTracker.reset()
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### pytest Integration
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
python -m pytest tests/ --threadcheck
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The plugin hooks into `pytest_runtest_call` and reports race warnings as test failures.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Architecture
|
|
255
|
+
|
|
256
|
+
### Static Analysis Pipeline
|
|
257
|
+
|
|
258
|
+
1. Parse source into AST
|
|
259
|
+
2. Identify shared mutable state: globals, nonlocals, class attributes (`self.x`), module-level mutable objects (lists, dicts, sets)
|
|
260
|
+
3. Detect thread creation sites (`threading.Thread`, `executor.submit/map`)
|
|
261
|
+
4. Cross-reference with lock usage (`with lock:`, nested `with lock1, lock2:`)
|
|
262
|
+
5. Assign confidence: HIGH (thread target, no lock), MEDIUM (thread present, no lock), LOW (suspicious pattern, no thread context)
|
|
263
|
+
6. Report findings with repair suggestions
|
|
264
|
+
|
|
265
|
+
### Runtime Detection Pipeline
|
|
266
|
+
|
|
267
|
+
1. Parse source into AST
|
|
268
|
+
2. Identify shared variables per function scope (`global`, `nonlocal` declarations)
|
|
269
|
+
3. Transform AST: inject `read_before()` for reads and `write_before()` for writes of shared variables; inject `lock_acquire()`/`lock_release()` around `with` blocks
|
|
270
|
+
4. Compile and execute transformed code under a tracker that maintains per-thread vector clocks
|
|
271
|
+
5. On lock acquire, merge the lock's clock into the thread's clock (happens-before)
|
|
272
|
+
6. On lock release, save the thread's clock to the lock
|
|
273
|
+
7. After execution, scan access log for conflicting operations (concurrent writes OR write-read pairs with no happens-before relationship)
|
|
274
|
+
8. Report detected races with thread IDs, source locations, and overlap counts
|
|
275
|
+
|
|
276
|
+
### Free-threading Compatibility Check
|
|
277
|
+
|
|
278
|
+
1. Parse project dependencies from `pyproject.toml` or `requirements.txt`
|
|
279
|
+
2. For each installed package, scan for C extension files (`.pyd`/`.so`)
|
|
280
|
+
3. If no C extensions found -> COMPATIBLE
|
|
281
|
+
4. If C extension filename contains free-threading ABI tag (`cp313t-`/`cpython-313t-`) -> COMPATIBLE
|
|
282
|
+
5. Otherwise -> NEEDS_VERIFICATION
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Project Structure
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
threadcheck/
|
|
290
|
+
|-- pyproject.toml
|
|
291
|
+
|-- src/threadcheck/
|
|
292
|
+
| |-- __init__.py
|
|
293
|
+
| |-- __main__.py
|
|
294
|
+
| |-- _version.py # single version source
|
|
295
|
+
| |-- _tid.py # platform thread ID (swapped per-platform in CI)
|
|
296
|
+
| |-- cli.py # argument parsing + dispatch
|
|
297
|
+
| |-- config.py # .threadcheckignore + pyproject.toml loader
|
|
298
|
+
| |-- pytest_plugin.py # --threadcheck flag for pytest
|
|
299
|
+
| |-- static/
|
|
300
|
+
| | |-- analyzer.py # static analysis entry point
|
|
301
|
+
| | |-- visitors.py # 5 AST visitors
|
|
302
|
+
| | |-- lock_tracker.py # lock usage analysis
|
|
303
|
+
| | \-- models.py # RaceWarning, Severity, Confidence
|
|
304
|
+
| |-- dynamic/
|
|
305
|
+
| | |-- __main__.py # run_script entry point
|
|
306
|
+
| | |-- transform.py # AST transformation engine
|
|
307
|
+
| | |-- tracker.py # runtime tracker with vector clocks
|
|
308
|
+
| | |-- clock.py # vector clock implementation
|
|
309
|
+
| | \-- hook.py # sys.meta_path import hook
|
|
310
|
+
| |-- compat/
|
|
311
|
+
| | |-- checker.py # C extension FT tag scanner
|
|
312
|
+
| | \-- models.py # FTCompatResult, CompatStatus
|
|
313
|
+
| \-- reporting/
|
|
314
|
+
| |-- formatter.py # terminal / JSON output
|
|
315
|
+
| |-- sarif.py # SARIF v2.1.0 output
|
|
316
|
+
| \-- html.py # HTML report output
|
|
317
|
+
|-- tests/
|
|
318
|
+
| |-- fixtures/ # 11 fixture files with known races
|
|
319
|
+
| |-- test_static_analyzer.py
|
|
320
|
+
| |-- test_dynamic_detector.py
|
|
321
|
+
| |-- test_formatter.py
|
|
322
|
+
| |-- test_sarif.py
|
|
323
|
+
| |-- test_compat.py
|
|
324
|
+
| |-- test_config.py
|
|
325
|
+
| \-- test_pytest_plugin.py
|
|
326
|
+
|-- demo/
|
|
327
|
+
| |-- race_example.py # sample with intentional races
|
|
328
|
+
| \-- run_demo.py # demo runner for all output formats
|
|
329
|
+
\-- README.md
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Platform Support
|
|
335
|
+
|
|
336
|
+
| Platform | Python | CI |
|
|
337
|
+
|---|---|---|
|
|
338
|
+
| Linux (x86_64) | 3.12, 3.13, 3.14 | ubuntu-latest |
|
|
339
|
+
| Windows (amd64) | 3.12, 3.13, 3.14 | windows-latest |
|
|
340
|
+
| macOS (ARM64) | 3.12, 3.13, 3.14 | macos-latest |
|
|
341
|
+
|
|
342
|
+
Thread IDs: uses `native_id` (gettid) on Linux/macOS, `threading.get_ident()` on Windows.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Roadmap
|
|
347
|
+
|
|
348
|
+
- **v0.0.1.2** (current): Round A (core gap fill) + Round B (DX) -- static and dynamic analysis, lock tracking, cross-module analysis, pytest plugin, FT compat checker, HTML reports, configuration, enhanced output
|
|
349
|
+
- **v0.2.0** (next): Round C -- `Thread.join()` happens-before, `threading.Atomic` support, function call chain tracking, deadlock detection
|
|
350
|
+
- **v1.0.0** (future): Round D -- GitHub Action, pre-commit hook, VS Code integration, stable API
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Limitations
|
|
355
|
+
|
|
356
|
+
- Static analysis may produce false positives (reports a race that cannot occur at runtime) and false negatives (misses races involving indirect sharing through aliases or containers)
|
|
357
|
+
- Runtime detection modifies the AST before execution; code that introspects its own source or frame objects may behave differently
|
|
358
|
+
- Runtime instrumentation incurs overhead (approximately 2-5x slowdown for typical code)
|
|
359
|
+
- Lock tracking supports `threading.Lock`, `threading.RLock`, and standard `with`-based patterns; other synchronization primitives (`threading.Event`, `threading.Condition`, third-party libraries) are not tracked
|
|
360
|
+
- Cross-module analysis handles `Thread(target=...)` and `executor.submit/map` but does not perform full inter-procedural data-flow analysis
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## License
|
|
365
|
+
|
|
366
|
+
MIT
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Contributing
|
|
371
|
+
|
|
372
|
+
Contributions are welcome. Please open an issue or submit a pull request on GitHub.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Threadcheck Development Plan
|
|
2
|
+
|
|
3
|
+
## Round A (Core Gap Fill) — Complete
|
|
4
|
+
- A1: `read_before()` injection in AST transform
|
|
5
|
+
- A2: Cross-module `Thread` target analysis
|
|
6
|
+
- A3: Exit codes (`scan` → 1 if warnings, `run` → 1 if races, `compat` → 1 if `NEEDS_VERIFICATION`)
|
|
7
|
+
- A4: `with lock1, lock2:` nested lock support
|
|
8
|
+
- A5: `executor.submit(func)` / `executor.map(func, ...)` static target extraction
|
|
9
|
+
|
|
10
|
+
## Round B (Developer Experience) — Complete
|
|
11
|
+
- B1: `.threadcheckignore` — gitignore-style + line suppression
|
|
12
|
+
- B2: `pyproject.toml` config — `[tool.threadcheck]` section
|
|
13
|
+
- B3: Enhanced terminal output — file grouping, severity icons, filename shortening
|
|
14
|
+
- B4: Output levels — `-q` / default / `-v`
|
|
15
|
+
- B5: HTML report — self-contained page with dark/light theme
|
|
16
|
+
- Config loader, static analyzer config integration, demo update, README rewrite
|
|
17
|
+
|
|
18
|
+
## Round C (Bug Fixes & Improvements) — Current
|
|
19
|
+
|
|
20
|
+
### Phase 1 — Quick Fixes
|
|
21
|
+
| # | Item | Status |
|
|
22
|
+
|---|------|--------|
|
|
23
|
+
| #12 | Windows output corruption — box-drawing chars in `format_dynamic_races` | Done |
|
|
24
|
+
| H | ThreadVisitor THREAD_USAGE dedup by target | Done |
|
|
25
|
+
| I | Cross-module: single-file mode needs `_collect_thread_targets` | Done |
|
|
26
|
+
| #11 | `_is_mutable_literal` misses `list()`/`set()`/`dict()` calls | Done |
|
|
27
|
+
| #10 | Remove `builtins._threadcheck_tracker` global namespace pollution | Done |
|
|
28
|
+
| K | TOCTOU in `_get_clock` — simplify lock pattern | Done |
|
|
29
|
+
|
|
30
|
+
### Phase 2 — Medium Changes
|
|
31
|
+
| # | Item | Status |
|
|
32
|
+
|---|------|--------|
|
|
33
|
+
| #14 | Inline `# threadcheck: ignore` (per-line + region) | Done |
|
|
34
|
+
| F | Subscript assignment (`shared_dict[key] = val`) not instrumented | Done |
|
|
35
|
+
| #13 | Manual `lock.acquire()`/`release()` not tracked by LockTracker | Done |
|
|
36
|
+
| G | ThreadPoolExecutor target not instrumented dynamically | Done |
|
|
37
|
+
|
|
38
|
+
### Phase 3 — Complex
|
|
39
|
+
| # | Item | Status |
|
|
40
|
+
|---|------|--------|
|
|
41
|
+
| #9 | Closure lock false positive — propagation of main thread clock to new threads | Done |
|
|
42
|
+
|
|
43
|
+
### Deferred to Round D
|
|
44
|
+
- #15: Call chain tracing — inter-procedural lock scope propagation
|
|
45
|
+
- threading.Atomic support
|
|
46
|
+
- Deadlock detection
|
|
47
|
+
- Thread.join() happens-before
|
|
48
|
+
- GitHub Action, pre-commit hook, VS Code integration
|
|
49
|
+
|
|
50
|
+
## Versions
|
|
51
|
+
- v0.0.1.2a1: Round B complete
|
|
52
|
+
- v0.0.1.2a5: Cross-platform CI + README rewrite
|
|
53
|
+
- v0.0.1.2a6: `__name__ == "__main__"` exec() fix
|