codesuture 0.5.0__tar.gz → 0.6.0__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 (55) hide show
  1. codesuture-0.6.0/.gitignore +33 -0
  2. codesuture-0.6.0/CHANGELOG.md +71 -0
  3. codesuture-0.6.0/MANIFEST.in +14 -0
  4. codesuture-0.6.0/PKG-INFO +167 -0
  5. codesuture-0.6.0/README.md +146 -0
  6. codesuture-0.6.0/codesuture/__init__.py +2 -0
  7. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/cli.py +3 -3
  8. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/guard_synthesizer.py +109 -2
  9. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/pattern_matcher.py +58 -7
  10. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/tracer.py +52 -11
  11. codesuture-0.6.0/codesuture.egg-info/PKG-INFO +167 -0
  12. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture.egg-info/SOURCES.txt +7 -0
  13. {codesuture-0.5.0 → codesuture-0.6.0}/pyproject.toml +2 -1
  14. codesuture-0.6.0/tests/__init__.py +0 -0
  15. codesuture-0.6.0/tests/closure_test.py +16 -0
  16. codesuture-0.6.0/tests/debug_gc.py +23 -0
  17. codesuture-0.6.0/tests/harness3.py +46 -0
  18. codesuture-0.5.0/PKG-INFO +0 -106
  19. codesuture-0.5.0/README.md +0 -85
  20. codesuture-0.5.0/codesuture/__init__.py +0 -1
  21. codesuture-0.5.0/codesuture.egg-info/PKG-INFO +0 -106
  22. {codesuture-0.5.0 → codesuture-0.6.0}/LICENSE +0 -0
  23. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/__main__.py +0 -0
  24. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/_eval_fix.py +0 -0
  25. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/audit.py +0 -0
  26. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/code_replacer.py +0 -0
  27. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/codesuture_fix.py +0 -0
  28. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/debuggee.py +0 -0
  29. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/diff_guard.py +0 -0
  30. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/explain.py +0 -0
  31. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/fingerprint.py +0 -0
  32. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/knowledge.py +0 -0
  33. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/middleware.py +0 -0
  34. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/persistence.py +0 -0
  35. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/plugins/__init__.py +0 -0
  36. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/plugins/autonomous.py +0 -0
  37. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/rewind.py +0 -0
  38. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/rollback.py +0 -0
  39. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/sandbox.py +0 -0
  40. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/shadow.py +0 -0
  41. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture/watcher.py +0 -0
  42. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture.egg-info/dependency_links.txt +0 -0
  43. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture.egg-info/entry_points.txt +0 -0
  44. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture.egg-info/requires.txt +0 -0
  45. {codesuture-0.5.0 → codesuture-0.6.0}/codesuture.egg-info/top_level.txt +0 -0
  46. {codesuture-0.5.0 → codesuture-0.6.0}/setup.cfg +0 -0
  47. {codesuture-0.5.0 → codesuture-0.6.0}/setup.py +0 -0
  48. {codesuture-0.5.0 → codesuture-0.6.0}/tests/test_codesuture_debuggee.py +0 -0
  49. {codesuture-0.5.0 → codesuture-0.6.0}/tests/test_e2e.py +0 -0
  50. {codesuture-0.5.0 → codesuture-0.6.0}/tests/test_guard_synthesizer.py +0 -0
  51. {codesuture-0.5.0 → codesuture-0.6.0}/tests/test_harness.py +0 -0
  52. {codesuture-0.5.0 → codesuture-0.6.0}/tests/test_harness2.py +0 -0
  53. {codesuture-0.5.0 → codesuture-0.6.0}/tests/test_new_guards.py +0 -0
  54. {codesuture-0.5.0 → codesuture-0.6.0}/tests/test_pattern_matcher.py +0 -0
  55. {codesuture-0.5.0 → codesuture-0.6.0}/tests/test_unknown_bug.py +0 -0
@@ -0,0 +1,33 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ .venv/
6
+ venv/
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+ .pytest_cache/
11
+ .coverage
12
+ htmlcov/
13
+
14
+ # CodeSuture specific
15
+ .codesuture_cache/
16
+ .codesuture_store/
17
+ .codesuture_knowledge/
18
+ .codesuture_fingerprints
19
+ .models/
20
+
21
+ # Exclude internal / dev scripts
22
+ codesuture_verify*.py
23
+ livepatch_verify*.py
24
+ realbugg.py
25
+ real.py
26
+ *.zip
27
+ .livepatch_*/
28
+ v4test/
29
+
30
+ # IDE / OS
31
+ .vscode/
32
+ .idea/
33
+ .DS_Store
@@ -0,0 +1,71 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.6.0] - 2026-05-12
9
+
10
+ ### Fixed
11
+ - PEP 659: Force de-specialization after `__code__` swap via
12
+ `ctypes.pythonapi.PyFunction_SetCode` — prevents CPython 3.11+
13
+ adaptive bytecode cache from ignoring injected patches
14
+ - Thread blindness: Install trace hook on all threads via
15
+ `threading.settrace` at startup, with `_install_trace_on_all_threads`
16
+ helper covering existing and future threads; added `threading.Lock`
17
+ for thread-safe patch store writes
18
+ - Exception table corruption: Guard injection now detects try/except
19
+ scope via `TryBegin`/`TryEnd` markers and redirects to function
20
+ entry-point injection to avoid corrupting `co_exceptiontable` offsets
21
+ in CPython 3.11+
22
+
23
+ ## [0.5.1] - 2026-05-11
24
+
25
+ ### Fixed
26
+ - propagate_patch: skip list/dict/set/generator comprehensions
27
+ instead of crashing with AttributeError on __code__
28
+ - key_guard, subscript_guard, chain_subscript_guard: infer
29
+ correct default type from downstream bytecode usage
30
+ (string methods -> "" default, numeric ops -> 0 default)
31
+ - KeyError on chained subscripts (e.g. request["headers"]["auth"].strip())
32
+ now produces a chain_subscript_guard instead of a simple key_guard,
33
+ preventing secondary TypeError from None subscript access
34
+
35
+ ## [0.5.0] - 2026-05-08
36
+
37
+ ### Added
38
+ - Async/await support (CO_COROUTINE frame detection) — automatic `RESUME 0` preservation for coroutine bytecode patching.
39
+ - Watch mode: `codesuture watch --max-restarts N` — subprocess loop with automatic crash-patch-restart cycle.
40
+ - Explain command: `codesuture explain [func_name]` — detailed table of active patches with safety assessment (LIKELY/RISKY/UNKNOWN).
41
+ - WSGI middleware: `CodeSutureMiddleware` — intercepts request handler exceptions, patches, and retries with `X-CodeSuture` response header.
42
+
43
+ ## [0.4.0] - 2026-05-07
44
+
45
+ ### Added
46
+ - `codesuture rollback` command to selectively remove persisted patches (`codesuture rollback <func>`, `--all`, and `--dry-run`).
47
+ - Three new guard types:
48
+ - `type_coercion_guard` for `TypeError` and `ValueError` during type conversions.
49
+ - `index_guard` for `IndexError` bounds checking.
50
+ - `key_guard` for safe dictionary `KeyError` fallbacks.
51
+ - Enhanced `--dry-run` mode with confidence levels (HIGH/MEDIUM/LOW) based on fingerprint registry hits.
52
+ - Full PyPI packaging structure (`pyproject.toml`, complete `README.md`, `CHANGELOG.md`).
53
+
54
+ ### Changed
55
+ - Migrated legacy guards `list_bound_guard` to `index_guard` and `dict_get_guard` to `key_guard` for consistency.
56
+ - Standardized CLI output format and improved error reporting.
57
+
58
+ ## [0.3.0] - 2026-05-06
59
+
60
+ ### Added
61
+ - Dark Upgrade D1: Semantic diff safety gate to prevent runaway bytecode corruption.
62
+ - Dark Upgrade D2: Caller-aware patch propagation to automatically fix closures and bound methods in-memory.
63
+ - Dark Upgrade D3: Shadow execution mode (`--shadow`) to monitor and warn when sentinel defaults leak downstream.
64
+ - Dark Upgrade D4: Patch expiry TTL warnings to nudge developers toward source-level fixes.
65
+ - Dark Upgrade D5: Bytecode fingerprint registry for instant cache hits on known crash patterns.
66
+ - Dark Upgrade D6: `codesuture audit` command for viewing all active patches in a formatted table.
67
+
68
+ ### Fixed
69
+ - Addressed Windows `UnicodeDecodeError` and `cp1252` terminal limitations by enforcing `utf-8` encoding.
70
+ - Resolved a race condition where patch persistence was executing after the code object swap, preventing correct caller identification.
71
+ - Fixed namespace pollution during nested patching.
@@ -0,0 +1,14 @@
1
+ include pyproject.toml
2
+ include README.md
3
+ include CHANGELOG.md
4
+ include LICENSE
5
+ include .gitignore
6
+
7
+ recursive-include codesuture *.py
8
+ recursive-include tests *.py
9
+
10
+ exclude codesuture_verify*.py
11
+ exclude livepatch_verify*.py
12
+ exclude realbugg.py
13
+ exclude real.py
14
+ exclude *.zip
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.4
2
+ Name: codesuture
3
+ Version: 0.6.0
4
+ Summary: Runtime Python bytecode patcher with guard knowledge base, persistence, and self-healing re-execution
5
+ License-Expression: MIT
6
+ Project-URL: Source, https://github.com/codesuture-py/codesuture
7
+ Keywords: bytecode,runtime,patching,self-healing,debugging,null-safety,resilience
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Software Development :: Debuggers
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: bytecode>=0.15.1
18
+ Provides-Extra: autonomous
19
+ Requires-Dist: llama-cpp-python; extra == "autonomous"
20
+ Dynamic: license-file
21
+
22
+ # CodeSuture
23
+
24
+ > Runtime Python bytecode patcher. Catches crashes, synthesizes guards, rewrites functions in-memory, and persists fixes across runs.
25
+
26
+ ---
27
+
28
+ ## What it does
29
+
30
+ CodeSuture sits between your Python program and its crashes. When an exception occurs, it intercepts the failing frame, disassembles the bytecode to identify the root cause, injects a deterministic guard directly into the function's code object, and retries execution — all without touching a single source file.
31
+
32
+ The fix persists. On subsequent runs, CodeSuture loads the patched bytecode before the first function call. The crash is gone until you choose to remove the patch.
33
+
34
+ It is a surgical tool for keeping programs running when structural failures — null access, missing keys, type mismatches, out-of-bounds reads — would otherwise bring them down.
35
+
36
+ ---
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install codesuture
42
+ ```
43
+
44
+ Python 3.11+ required.
45
+
46
+ ---
47
+
48
+ ## Quick start
49
+
50
+ ```bash
51
+ codesuture run your_script.py
52
+ ```
53
+
54
+ ```
55
+ [CodeSuture] Caught AttributeError: 'NoneType' object has no attribute 'bio'
56
+ [CodeSuture] Applying null_guard on 'profile' ...
57
+ [CodeSuture] Patch applied to get_bio().
58
+ [CodeSuture] Re-executing after 1 patch(es)...
59
+
60
+ Session summary:
61
+ Patches applied: 1
62
+ ```
63
+
64
+ Run it again:
65
+
66
+ ```
67
+ [CodeSuture] Already healed, skipping: loaded persistent patch for get_bio
68
+ [CodeSuture] Session summary:
69
+ Patches applied: 0
70
+ ```
71
+
72
+ ---
73
+
74
+ ## How it works
75
+
76
+ 1. **Catch** — A `sys.settrace` callback intercepts exceptions at the exact frame and bytecode offset where they occur.
77
+ 2. **Analyze** — The pattern matcher disassembles the function's bytecode, walks the instruction chain, and identifies the failing variable or operation.
78
+ 3. **Patch** — The guard synthesizer injects new bytecode instructions into the function's code object in memory. A semantic diff gate rejects patches that would change too much of the function's logic.
79
+ 4. **Rewind** — Execution restarts from the patched function. The guard prevents the crash from recurring.
80
+ 5. **Persist** — The patched code object is serialized to `.codesuture_store/` with JSON metadata. On subsequent runs it loads before execution begins.
81
+
82
+ No source files are modified at any point.
83
+
84
+ ---
85
+
86
+ ## Supported guard types
87
+
88
+ | Guard type | Triggers on | Example |
89
+ |---|---|---|
90
+ | `null_guard` | `AttributeError` on `None` | `user.profile.bio` when `profile` is `None` |
91
+ | `index_guard` | `IndexError` | `items[10]` when `len(items) == 2` |
92
+ | `key_guard` | `KeyError` | `cfg["timeout"]` when key is missing |
93
+ | `subscript_guard` | `TypeError` subscripting `None` | `data["key"]` when `data` is `None` |
94
+ | `chain_subscript_guard` | Nested subscript on `None` | `data["user"]["name"]` |
95
+ | `type_coercion_guard` | `TypeError` on conversion | `int("not_a_number")` |
96
+ | `division_guard` | `ZeroDivisionError` | `x / count` when `count == 0` |
97
+ | `str_coerce_guard` | `TypeError` on string concat | `"age: " + 25` |
98
+ | `file_guard` | `FileNotFoundError` | `open(path)` when file is missing |
99
+ | `callable_guard` | `TypeError` calling `None` | `func()` when `func` is `None` |
100
+
101
+ ---
102
+
103
+ ## CLI reference
104
+
105
+ | Command | Flags | What it does |
106
+ |---|---|---|
107
+ | `codesuture run <script>` | | Run script with live patching enabled |
108
+ | `codesuture run <script>` | `--verbose` | Show patch diffs and instruction deltas |
109
+ | `codesuture run <script>` | `--shadow` | Warn when patched functions return sentinel values |
110
+ | `codesuture run <script>` | `--dry-run` | Preview what would be patched without applying |
111
+ | `codesuture run <script>` | `--ttl DAYS` | Set patch expiry in days (default: 7) |
112
+ | `codesuture run <script>` | `--retries N` | Max re-execution attempts per crash (default: 3) |
113
+ | `codesuture watch <script>` | `--max-restarts N` | Run continuously, restart after each patch |
114
+ | `codesuture audit` | | Show all active patches in a formatted table |
115
+ | `codesuture explain` | | Human-readable breakdown of what each patch changed |
116
+ | `codesuture explain <name>` | | Explain one function's patch |
117
+ | `codesuture rollback <name>` | | Remove one persisted patch |
118
+ | `codesuture rollback` | `--all` | Remove all patches and the fingerprint registry |
119
+ | `codesuture rollback` | `--dry-run` | Preview what would be removed |
120
+
121
+ ---
122
+
123
+ ## Runtime Intelligence
124
+
125
+ Beyond basic patching, CodeSuture includes a set of higher-order behaviors that make it safe to use in real codebases:
126
+
127
+ **Semantic diff gate** — Patches that would modify too many instructions relative to the guard type are automatically rejected. The engine will never corrupt a complex function to patch a simple crash.
128
+
129
+ **Caller-aware propagation** — After patching a function, CodeSuture uses `gc.get_referrers` to find every live reference to the original code object — closures, bound methods, partials — and updates them all. No in-memory copy of the broken function survives.
130
+
131
+ **Shadow execution mode** — `--shadow` monitors the return value of patched functions. If a sentinel default (`""`, `0`, `None`) leaks into downstream logic, CodeSuture logs a warning before it causes a second failure.
132
+
133
+ **Patch expiry** — Every persisted patch carries a configurable TTL. When a patch ages past its limit, CodeSuture logs a reminder that the underlying bug should be addressed in source code. Patches are scaffolding, not permanent fixes.
134
+
135
+ **Bytecode fingerprint registry** — Crash sites are hashed by their surrounding instruction window. When the same crash pattern appears again, CodeSuture applies the known guard directly without re-running analysis.
136
+
137
+ **Audit trail** — `codesuture audit` displays every active patch: function name, guard type, target variable, default value, age, and rollback instructions. `codesuture explain` gives a plain-language breakdown of exactly what each patch changed and whether the default value is safe for downstream consumers.
138
+
139
+ ---
140
+
141
+ ## What CodeSuture is not
142
+
143
+ **Not a logger.** It does not record exceptions and move on. It patches the function and retries.
144
+
145
+ **Not a fuzzer or static analyzer.** It operates at runtime on live bytecode, not on source.
146
+
147
+ **Not autonomous.** Patches should be reviewed via `codesuture audit` and `codesuture explain`. The goal is to keep your program running while you address the root cause — not to replace the fix.
148
+
149
+ ---
150
+
151
+ ## Limitations
152
+
153
+ **Python 3.11+ only.** CodeSuture depends on bytecode structures introduced in CPython 3.11. Earlier versions are not supported.
154
+
155
+ **List and dict comprehensions are not patchable.** Comprehensions are implemented as anonymous nested code objects in CPython. CodeSuture detects crashes inside them and logs a warning, but does not attempt to patch them. Refactor the comprehension into a named function to enable patching.
156
+
157
+ **Semantic bugs are not patchable.** CodeSuture fixes structural crashes — null access, missing keys, type mismatches, bounds errors. It cannot fix logic errors where the code runs but produces wrong results.
158
+
159
+ **Single-process scope.** Patches apply per-process. Multi-process applications need a CodeSuture instance per worker. The `.codesuture_store/` directory is shared on disk, so patches load correctly on restart.
160
+
161
+ **Web server support requires Python 3.11+ with `threading.settrace` active.** Tested with `socketserver` and `threading.Thread`. ASGI frameworks (FastAPI, Starlette) require the ASGI middleware wrapper.
162
+
163
+ ---
164
+
165
+ ## License
166
+
167
+ MIT. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,146 @@
1
+ # CodeSuture
2
+
3
+ > Runtime Python bytecode patcher. Catches crashes, synthesizes guards, rewrites functions in-memory, and persists fixes across runs.
4
+
5
+ ---
6
+
7
+ ## What it does
8
+
9
+ CodeSuture sits between your Python program and its crashes. When an exception occurs, it intercepts the failing frame, disassembles the bytecode to identify the root cause, injects a deterministic guard directly into the function's code object, and retries execution — all without touching a single source file.
10
+
11
+ The fix persists. On subsequent runs, CodeSuture loads the patched bytecode before the first function call. The crash is gone until you choose to remove the patch.
12
+
13
+ It is a surgical tool for keeping programs running when structural failures — null access, missing keys, type mismatches, out-of-bounds reads — would otherwise bring them down.
14
+
15
+ ---
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install codesuture
21
+ ```
22
+
23
+ Python 3.11+ required.
24
+
25
+ ---
26
+
27
+ ## Quick start
28
+
29
+ ```bash
30
+ codesuture run your_script.py
31
+ ```
32
+
33
+ ```
34
+ [CodeSuture] Caught AttributeError: 'NoneType' object has no attribute 'bio'
35
+ [CodeSuture] Applying null_guard on 'profile' ...
36
+ [CodeSuture] Patch applied to get_bio().
37
+ [CodeSuture] Re-executing after 1 patch(es)...
38
+
39
+ Session summary:
40
+ Patches applied: 1
41
+ ```
42
+
43
+ Run it again:
44
+
45
+ ```
46
+ [CodeSuture] Already healed, skipping: loaded persistent patch for get_bio
47
+ [CodeSuture] Session summary:
48
+ Patches applied: 0
49
+ ```
50
+
51
+ ---
52
+
53
+ ## How it works
54
+
55
+ 1. **Catch** — A `sys.settrace` callback intercepts exceptions at the exact frame and bytecode offset where they occur.
56
+ 2. **Analyze** — The pattern matcher disassembles the function's bytecode, walks the instruction chain, and identifies the failing variable or operation.
57
+ 3. **Patch** — The guard synthesizer injects new bytecode instructions into the function's code object in memory. A semantic diff gate rejects patches that would change too much of the function's logic.
58
+ 4. **Rewind** — Execution restarts from the patched function. The guard prevents the crash from recurring.
59
+ 5. **Persist** — The patched code object is serialized to `.codesuture_store/` with JSON metadata. On subsequent runs it loads before execution begins.
60
+
61
+ No source files are modified at any point.
62
+
63
+ ---
64
+
65
+ ## Supported guard types
66
+
67
+ | Guard type | Triggers on | Example |
68
+ |---|---|---|
69
+ | `null_guard` | `AttributeError` on `None` | `user.profile.bio` when `profile` is `None` |
70
+ | `index_guard` | `IndexError` | `items[10]` when `len(items) == 2` |
71
+ | `key_guard` | `KeyError` | `cfg["timeout"]` when key is missing |
72
+ | `subscript_guard` | `TypeError` subscripting `None` | `data["key"]` when `data` is `None` |
73
+ | `chain_subscript_guard` | Nested subscript on `None` | `data["user"]["name"]` |
74
+ | `type_coercion_guard` | `TypeError` on conversion | `int("not_a_number")` |
75
+ | `division_guard` | `ZeroDivisionError` | `x / count` when `count == 0` |
76
+ | `str_coerce_guard` | `TypeError` on string concat | `"age: " + 25` |
77
+ | `file_guard` | `FileNotFoundError` | `open(path)` when file is missing |
78
+ | `callable_guard` | `TypeError` calling `None` | `func()` when `func` is `None` |
79
+
80
+ ---
81
+
82
+ ## CLI reference
83
+
84
+ | Command | Flags | What it does |
85
+ |---|---|---|
86
+ | `codesuture run <script>` | | Run script with live patching enabled |
87
+ | `codesuture run <script>` | `--verbose` | Show patch diffs and instruction deltas |
88
+ | `codesuture run <script>` | `--shadow` | Warn when patched functions return sentinel values |
89
+ | `codesuture run <script>` | `--dry-run` | Preview what would be patched without applying |
90
+ | `codesuture run <script>` | `--ttl DAYS` | Set patch expiry in days (default: 7) |
91
+ | `codesuture run <script>` | `--retries N` | Max re-execution attempts per crash (default: 3) |
92
+ | `codesuture watch <script>` | `--max-restarts N` | Run continuously, restart after each patch |
93
+ | `codesuture audit` | | Show all active patches in a formatted table |
94
+ | `codesuture explain` | | Human-readable breakdown of what each patch changed |
95
+ | `codesuture explain <name>` | | Explain one function's patch |
96
+ | `codesuture rollback <name>` | | Remove one persisted patch |
97
+ | `codesuture rollback` | `--all` | Remove all patches and the fingerprint registry |
98
+ | `codesuture rollback` | `--dry-run` | Preview what would be removed |
99
+
100
+ ---
101
+
102
+ ## Runtime Intelligence
103
+
104
+ Beyond basic patching, CodeSuture includes a set of higher-order behaviors that make it safe to use in real codebases:
105
+
106
+ **Semantic diff gate** — Patches that would modify too many instructions relative to the guard type are automatically rejected. The engine will never corrupt a complex function to patch a simple crash.
107
+
108
+ **Caller-aware propagation** — After patching a function, CodeSuture uses `gc.get_referrers` to find every live reference to the original code object — closures, bound methods, partials — and updates them all. No in-memory copy of the broken function survives.
109
+
110
+ **Shadow execution mode** — `--shadow` monitors the return value of patched functions. If a sentinel default (`""`, `0`, `None`) leaks into downstream logic, CodeSuture logs a warning before it causes a second failure.
111
+
112
+ **Patch expiry** — Every persisted patch carries a configurable TTL. When a patch ages past its limit, CodeSuture logs a reminder that the underlying bug should be addressed in source code. Patches are scaffolding, not permanent fixes.
113
+
114
+ **Bytecode fingerprint registry** — Crash sites are hashed by their surrounding instruction window. When the same crash pattern appears again, CodeSuture applies the known guard directly without re-running analysis.
115
+
116
+ **Audit trail** — `codesuture audit` displays every active patch: function name, guard type, target variable, default value, age, and rollback instructions. `codesuture explain` gives a plain-language breakdown of exactly what each patch changed and whether the default value is safe for downstream consumers.
117
+
118
+ ---
119
+
120
+ ## What CodeSuture is not
121
+
122
+ **Not a logger.** It does not record exceptions and move on. It patches the function and retries.
123
+
124
+ **Not a fuzzer or static analyzer.** It operates at runtime on live bytecode, not on source.
125
+
126
+ **Not autonomous.** Patches should be reviewed via `codesuture audit` and `codesuture explain`. The goal is to keep your program running while you address the root cause — not to replace the fix.
127
+
128
+ ---
129
+
130
+ ## Limitations
131
+
132
+ **Python 3.11+ only.** CodeSuture depends on bytecode structures introduced in CPython 3.11. Earlier versions are not supported.
133
+
134
+ **List and dict comprehensions are not patchable.** Comprehensions are implemented as anonymous nested code objects in CPython. CodeSuture detects crashes inside them and logs a warning, but does not attempt to patch them. Refactor the comprehension into a named function to enable patching.
135
+
136
+ **Semantic bugs are not patchable.** CodeSuture fixes structural crashes — null access, missing keys, type mismatches, bounds errors. It cannot fix logic errors where the code runs but produces wrong results.
137
+
138
+ **Single-process scope.** Patches apply per-process. Multi-process applications need a CodeSuture instance per worker. The `.codesuture_store/` directory is shared on disk, so patches load correctly on restart.
139
+
140
+ **Web server support requires Python 3.11+ with `threading.settrace` active.** Tested with `socketserver` and `threading.Thread`. ASGI frameworks (FastAPI, Starlette) require the ASGI middleware wrapper.
141
+
142
+ ---
143
+
144
+ ## License
145
+
146
+ MIT. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,2 @@
1
+ __version__ = "0.6.0"
2
+
@@ -1,11 +1,11 @@
1
1
  import sys
2
2
  import argparse
3
- from codesuture.tracer import install, uninstall
3
+ from codesuture.tracer import install, uninstall, _install_trace_on_all_threads
4
4
 
5
5
  def main():
6
6
  parser = argparse.ArgumentParser(prog='codesuture',
7
7
  description='Runtime Python bytecode patcher with self-healing re-execution')
8
- parser.add_argument('--version', action='version', version='codesuture 0.5.0')
8
+ parser.add_argument('--version', action='version', version='codesuture 0.6.0')
9
9
  sub = parser.add_subparsers(dest='command', required=True)
10
10
 
11
11
  run_parser = sub.add_parser('run', help='Run a script with live patching')
@@ -102,7 +102,7 @@ def main():
102
102
  patched_before = tracer.stats['patched']
103
103
  tracer._handled_exc_ids.clear()
104
104
  try:
105
- sys.settrace(tracer)
105
+ _install_trace_on_all_threads(tracer)
106
106
  globs = make_persisted_patch_globals(
107
107
  "__main__",
108
108
  {'__name__': '__main__', '__file__': args.script},
@@ -1,9 +1,30 @@
1
- """
1
+ """
2
2
  Synthesises guard + original bytecode for all deterministic strategies.
3
3
  """
4
+ import ctypes
4
5
  from bytecode import Bytecode, Instr, Label, Compare
5
6
  from codesuture.pattern_matcher import PatchSpec
6
7
 
8
+ def _force_despecialize(func):
9
+ """
10
+ Force CPython 3.11+ to abandon its adaptive
11
+ instruction cache for this function.
12
+ After __code__ replacement, the interpreter must
13
+ re-read the new bytecode from scratch.
14
+ """
15
+ try:
16
+ # PyFunction_SetCode forces de-specialization
17
+ # by going through the official C API path
18
+ # rather than the Python attribute setter.
19
+ ctypes.pythonapi.PyFunction_SetCode(
20
+ ctypes.py_object(func),
21
+ ctypes.py_object(func.__code__)
22
+ )
23
+ except Exception:
24
+ pass # Non-fatal: patch still applied, may not
25
+ # take effect until next function cold-start
26
+
27
+
7
28
  class PatchValidationError(Exception):
8
29
  pass
9
30
 
@@ -25,6 +46,22 @@ def validate_patch(original_code, patched_code):
25
46
 
26
47
  def propagate_patch(original_func, patched_code) -> int:
27
48
  import gc
49
+ import logging
50
+ if original_func is None:
51
+ return 0
52
+ if not hasattr(original_func, '__code__'):
53
+ return 0
54
+ name = getattr(original_func, '__qualname__', '') or \
55
+ getattr(original_func, '__name__', '')
56
+ if '<listcomp>' in name or '<genexpr>' in name or \
57
+ '<dictcomp>' in name or '<setcomp>' in name:
58
+ import logging
59
+ logging.getLogger(__name__).debug(
60
+ "[CodeSuture] Skipping %s — "
61
+ "comprehensions are not patchable via __code__", name
62
+ )
63
+ return 0
64
+
28
65
  original_code = original_func.__code__
29
66
  propagated = 0
30
67
 
@@ -35,21 +72,91 @@ def propagate_patch(original_func, patched_code) -> int:
35
72
  if hasattr(ref, '__func__') and hasattr(ref.__func__, '__code__'):
36
73
  if ref.__func__.__code__ is original_code:
37
74
  ref.__func__.__code__ = patched_code
75
+ _force_despecialize(ref.__func__)
38
76
  propagated += 1
39
77
 
40
78
  elif hasattr(ref, '__code__') and ref.__code__ is original_code:
41
79
  ref.__code__ = patched_code
80
+ _force_despecialize(ref)
42
81
  propagated += 1
43
82
 
44
83
  original_func.__code__ = patched_code
84
+ _force_despecialize(original_func)
45
85
 
46
86
  if propagated > 0:
47
87
  print(f"[CodeSuture] Propagated patch to {propagated} additional "
48
88
  f"live reference(s) of {original_func.__qualname__}.")
49
89
  return propagated
50
90
 
91
+ def _is_inside_try_block(code):
92
+ """Return True if any BINARY_SUBSCR or crash-relevant opcode
93
+ falls inside an exception handler range (TryBegin/TryEnd).
94
+ Uses the bytecode library's TryBegin/TryEnd markers."""
95
+ import sys
96
+ if sys.version_info < (3, 11):
97
+ return False
98
+ try:
99
+ from bytecode import TryBegin, TryEnd
100
+ bc = Bytecode.from_code(code)
101
+ depth = 0
102
+ has_subscr_in_try = False
103
+ for item in bc:
104
+ if isinstance(item, TryBegin):
105
+ depth += 1
106
+ elif isinstance(item, TryEnd):
107
+ depth = max(0, depth - 1)
108
+ elif depth > 0 and isinstance(item, Instr):
109
+ if item.name in ('BINARY_SUBSCR', 'LOAD_ATTR', 'LOAD_METHOD',
110
+ 'BINARY_OP', 'BINARY_TRUE_DIVIDE'):
111
+ has_subscr_in_try = True
112
+ break
113
+ return has_subscr_in_try
114
+ except Exception:
115
+ return False
116
+
117
+ def _build_entry_point_null_guard(original_code, var_name, default):
118
+ """Build a guard injected at the function entry point (after RESUME).
119
+ Checks if var_name is None and replaces it with default.
120
+ Safe for use when the crash site is inside a try block."""
121
+ bc = Bytecode.from_code(original_code)
122
+ skip = Label()
123
+ patch = [
124
+ Instr('LOAD_FAST', var_name),
125
+ Instr('LOAD_CONST', None),
126
+ Instr('IS_OP', 0),
127
+ Instr('POP_JUMP_FORWARD_IF_FALSE', skip),
128
+ Instr('LOAD_CONST', default),
129
+ Instr('RETURN_VALUE'),
130
+ skip
131
+ ]
132
+ idx = 0
133
+ for i, instr in enumerate(bc):
134
+ if isinstance(instr, Instr) and instr.name == 'RESUME':
135
+ idx = i + 1
136
+ break
137
+ for instr in reversed(patch):
138
+ bc.insert(idx, instr)
139
+ return bc
140
+
141
+ # Strategies that inject inline at the crash site (replace BINARY_SUBSCR etc.)
142
+ # These are the ones that can corrupt exception tables when inside try blocks.
143
+ _INLINE_STRATEGIES = frozenset({
144
+ 'subscript_guard', 'key_guard', 'dict_get_guard',
145
+ 'chain_subscript_guard', 'division_guard',
146
+ })
147
+
51
148
  def synthesize_guarded_code(original_code, spec: PatchSpec) -> Bytecode:
52
- if spec.strategy in ('subscript_guard', 'key_guard', 'dict_get_guard'):
149
+ # Bug 3 fix: If the crash site is inside a try/except block and we
150
+ # would normally inject inline, redirect to an entry-point guard
151
+ # to avoid corrupting co_exceptiontable offsets on CPython 3.11+.
152
+ if spec.strategy in _INLINE_STRATEGIES and _is_inside_try_block(original_code):
153
+ import logging
154
+ logging.getLogger(__name__).debug(
155
+ "[CodeSuture] Crash inside try block — redirecting %s to entry-point guard",
156
+ spec.strategy
157
+ )
158
+ res = _build_entry_point_null_guard(original_code, spec.var_name, spec.default_value)
159
+ elif spec.strategy in ('subscript_guard', 'key_guard', 'dict_get_guard'):
53
160
  res = _build_subscript_guarded_code(original_code, spec.var_name, spec.key_name, spec.default_value)
54
161
  elif spec.strategy == 'chain_subscript_guard':
55
162
  res = _build_chain_subscript_guarded_code(original_code, spec.var_name, spec.key_name, spec.default_value)