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.
Files changed (86) hide show
  1. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/.github/workflows/release.yml +48 -6
  2. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/.github/workflows/test.yml +3 -2
  3. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/.gitignore +6 -0
  4. threadcheck-0.0.1.2/PKG-INFO +372 -0
  5. threadcheck-0.0.1.2/PLAN.md +53 -0
  6. threadcheck-0.0.1.2/README.md +349 -0
  7. threadcheck-0.0.1.2/README_CN.md +356 -0
  8. threadcheck-0.0.1.2/demo/race_example.py +71 -0
  9. threadcheck-0.0.1.2/demo/run_demo.py +87 -0
  10. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/pyproject.toml +5 -0
  11. threadcheck-0.0.1.2/release_body.md +10 -0
  12. threadcheck-0.0.1.2/scripts/tag_wheel.py +61 -0
  13. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/__init__.py +1 -0
  14. threadcheck-0.0.1.2/src/threadcheck/_tid.py +13 -0
  15. threadcheck-0.0.1.2/src/threadcheck/_tid_linux.py +9 -0
  16. threadcheck-0.0.1.2/src/threadcheck/_tid_macos.py +9 -0
  17. threadcheck-0.0.1.2/src/threadcheck/_tid_win.py +5 -0
  18. threadcheck-0.0.1.2/src/threadcheck/_version.py +1 -0
  19. threadcheck-0.0.1.2/src/threadcheck/cli.py +241 -0
  20. threadcheck-0.0.1.2/src/threadcheck/compat/__init__.py +4 -0
  21. threadcheck-0.0.1.2/src/threadcheck/compat/checker.py +95 -0
  22. threadcheck-0.0.1.2/src/threadcheck/compat/models.py +40 -0
  23. threadcheck-0.0.1.2/src/threadcheck/config.py +104 -0
  24. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/dynamic/__main__.py +19 -3
  25. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/dynamic/hook.py +0 -2
  26. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/dynamic/tracker.py +21 -26
  27. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/dynamic/transform.py +110 -29
  28. threadcheck-0.0.1.2/src/threadcheck/reporting/__init__.py +12 -0
  29. threadcheck-0.0.1.2/src/threadcheck/reporting/formatter.py +251 -0
  30. threadcheck-0.0.1.2/src/threadcheck/reporting/html.py +168 -0
  31. threadcheck-0.0.1.2/src/threadcheck/reporting/types.py +8 -0
  32. threadcheck-0.0.1.2/src/threadcheck/static/analyzer.py +198 -0
  33. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/static/lock_tracker.py +25 -2
  34. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/static/visitors.py +9 -1
  35. threadcheck-0.0.1.2/tests/fixtures/closure_lock_safe.py +21 -0
  36. threadcheck-0.0.1.2/tests/fixtures/cross_module_main.py +7 -0
  37. threadcheck-0.0.1.2/tests/fixtures/cross_module_worker.py +7 -0
  38. threadcheck-0.0.1.2/tests/fixtures/dynamic_read_race.py +21 -0
  39. threadcheck-0.0.1.2/tests/fixtures/dynamic_subscript_race.py +26 -0
  40. threadcheck-0.0.1.2/tests/fixtures/futures_race.py +16 -0
  41. threadcheck-0.0.1.2/tests/fixtures/inline_ignore.py +32 -0
  42. threadcheck-0.0.1.2/tests/fixtures/manual_lock.py +20 -0
  43. threadcheck-0.0.1.2/tests/fixtures/name_main_guard.py +17 -0
  44. threadcheck-0.0.1.2/tests/fixtures/nested_locks.py +18 -0
  45. threadcheck-0.0.1.2/tests/test_compat.py +173 -0
  46. threadcheck-0.0.1.2/tests/test_config.py +108 -0
  47. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/test_dynamic_detector.py +73 -0
  48. threadcheck-0.0.1.2/tests/test_formatter.py +191 -0
  49. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/test_pytest_plugin.py +2 -1
  50. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/test_static_analyzer.py +65 -0
  51. threadcheck-0.0.1/PKG-INFO +0 -248
  52. threadcheck-0.0.1/PLAN.md +0 -292
  53. threadcheck-0.0.1/README.md +0 -225
  54. threadcheck-0.0.1/README_CN.md +0 -230
  55. threadcheck-0.0.1/demo/race_example.py +0 -21
  56. threadcheck-0.0.1/demo/run_demo.py +0 -44
  57. threadcheck-0.0.1/release_body.md +0 -16
  58. threadcheck-0.0.1/src/threadcheck/_version.py +0 -1
  59. threadcheck-0.0.1/src/threadcheck/cli.py +0 -89
  60. threadcheck-0.0.1/src/threadcheck/reporting/formatter.py +0 -33
  61. threadcheck-0.0.1/src/threadcheck/reporting/types.py +0 -3
  62. threadcheck-0.0.1/src/threadcheck/static/__init__.py +0 -0
  63. threadcheck-0.0.1/src/threadcheck/static/analyzer.py +0 -104
  64. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/.github/workflows/diagnose_hook.py +0 -0
  65. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/.github/workflows/diagnose_pytest.py +0 -0
  66. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/LICENSE +0 -0
  67. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/__main__.py +0 -0
  68. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/dynamic/__init__.py +0 -0
  69. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/dynamic/clock.py +0 -0
  70. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/pytest_plugin.py +0 -0
  71. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/reporting/sarif.py +0 -0
  72. {threadcheck-0.0.1/src/threadcheck/reporting → threadcheck-0.0.1.2/src/threadcheck/static}/__init__.py +0 -0
  73. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/src/threadcheck/static/models.py +0 -0
  74. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/conftest.py +0 -0
  75. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/class_attribute_safe.py +0 -0
  76. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/class_race.py +0 -0
  77. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/class_safe.py +0 -0
  78. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/dynamic_race.py +0 -0
  79. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/dynamic_safe.py +0 -0
  80. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/no_race_simple.py +0 -0
  81. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/nonlocal_race.py +0 -0
  82. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/safe_with_lock.py +0 -0
  83. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/shared_list.py +0 -0
  84. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/simple_global.py +0 -0
  85. {threadcheck-0.0.1 → threadcheck-0.0.1.2}/tests/fixtures/thread_subclass_race.py +0 -0
  86. {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: test
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: Set up Python
119
- uses: actions/setup-python@v5
158
+ - name: Download all wheels
159
+ uses: actions/download-artifact@v4
120
160
  with:
121
- python-version: "3.12"
161
+ pattern: wheel-*
162
+ path: dist/
163
+ merge-multiple: true
122
164
 
123
- - name: Build package
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 on Python ${{ matrix.python-version }}
12
- runs-on: ubuntu-latest
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:
@@ -28,3 +28,9 @@ tmp_diagnose/
28
28
 
29
29
  # Lock files
30
30
  uv.lock
31
+
32
+ # Agent Files
33
+ AGENTS.md
34
+
35
+ # Generated reports
36
+ demo/report.html
@@ -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
+ [![Python](https://img.shields.io/badge/python-3.12%2B-blue)](https://www.python.org)
27
+ [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
28
+ [![Platform](https://img.shields.io/badge/platform-linux%20%7C%20windows%20%7C%20macos-lightgrey)]()
29
+ [![CI](https://github.com/ChidcGithub/Threadcheck/actions/workflows/test.yml/badge.svg)](https://github.com/ChidcGithub/Threadcheck/actions/workflows/test.yml)
30
+ [![Ruff](https://img.shields.io/badge/code%20style-ruff-000000)](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