Skip to content

Commit 0344643

Browse files
committed
docs(P-034): address CodeRabbit review — narrow scope claims, fix [Conditional] example
- Motivation no longer implies the quarantine is a "substitute" for ClrMD (it already says the opposite later — was self-contradictory) or that LifetimeGuard "closes"/detects D5 ownership-transfer itself; it only surfaces the resulting misuse (double-dispose/use-after-dispose), regardless of root cause. - Non-goals: [ConditionalAttribute] can only decorate methods, not a throw statement — reworded to a #if DEBUG/TEST guard or a [Conditional]-attributed helper method.
1 parent b6b4b42 commit 0344643

1 file changed

Lines changed: 22 additions & 13 deletions

File tree

docs/proposals/P-034-runtime-lifetime-guard.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ down before anyone re-derives it:
2626

2727
So the static half of "enterprise lifetime checking" is not a gap — it's shipped.
2828
What's actually missing is the **dynamic/runtime half**: a lightweight guard
29-
that fails loudly *at run time* for exactly the two cases static analysis
30-
structurally cannot close — D5 (ownership transferred through an unmodeled
31-
callee) and cross-thread disposal races (both explicit non-goals of P-005) —
32-
plus a cheap, cross-platform substitute for OwnAudit's ClrMD heap walk that
33-
runs in an ordinary unit test, no Windows stand required.
29+
that fails loudly *at run time* when misuse actually happens — a double-dispose
30+
or a post-dispose access, whether it originates from an ownership hand-off
31+
own-check doesn't model (P-005 D5) or from a cross-thread race (both explicit
32+
non-goals of P-005) — plus a cheap, ClrMD-independent test-time check that runs
33+
in an ordinary unit test, no Windows stand required. Neither piece models *why*
34+
an object ended up misused, the way OwnAudit's phase 5 or a static prover would;
35+
they only make the misuse itself loud the moment it happens.
3436

3537
## What already exists (do not re-derive)
3638

@@ -71,10 +73,11 @@ Two small, independent, opt-in runtime helpers (a "diagnostic mode", not a
7173
shipped allocator):
7274

7375
**1. `LifetimeGuard` base / wrapper — loud instead of silent.**
74-
A `DEBUG`/`TEST`-only `IDisposable` base that turns the two silent failure
75-
modes static analysis cannot close (D5 unknown transfer, cross-thread race)
76-
into an immediate `ObjectDisposedException`/`InvalidOperationException` instead
77-
of corrupting state quietly:
76+
A `DEBUG`/`TEST`-only `IDisposable` base that turns silent misuse — a
77+
double-dispose, or an access after `Dispose()` — into an immediate
78+
`ObjectDisposedException`/`InvalidOperationException` instead of corrupting
79+
state quietly, regardless of whether the root cause was an unmodeled
80+
ownership hand-off (P-005 D5) or a cross-thread race:
7881

7982
```csharp
8083
public abstract class LifetimeGuard : IDisposable
@@ -94,8 +97,11 @@ public abstract class LifetimeGuard : IDisposable
9497

9598
This does not replace `OWN002`/`OWN003` — those catch the mistake at compile
9699
time, for free, when the pattern is intraprocedural. `LifetimeGuard` catches
97-
what's left: ownership handed through a callee own-check doesn't model
98-
(P-005 D5), and the cross-thread race P-005 explicitly declines to touch.
100+
what's left: it surfaces misuse *at the point it happens*, even when the
101+
underlying cause was ownership handed through a callee own-check doesn't model
102+
(P-005 D5) or the cross-thread race P-005 explicitly declines to touch — it
103+
does not itself model or detect the hand-off/race, only the resulting
104+
double-dispose or post-dispose access.
99105

100106
**2. Disposal quarantine for tests — a ClrMD-free complement to phase 5.**
101107
An opt-in `ITrackedDisposable` + an ambient registry that records the
@@ -129,8 +135,11 @@ debug assertion than an auditor.
129135
- Not a production-safe pattern as-is: throwing from `Dispose()` is a real
130136
behavior change (already-suppressed `Dispose` exceptions in `finally`/`using`
131137
chains can mask the original exception) — `LifetimeGuard` must ship
132-
`DEBUG`/`TEST`-gated (e.g. `[Conditional]` on the throw, or a config flag),
133-
never silently opt production code into new exceptions.
138+
`DEBUG`/`TEST`-gated: wrap the throw in `#if DEBUG` / `#if TEST`, or route it
139+
through a small helper method decorated `[Conditional("DEBUG")]` (the
140+
attribute only applies to methods, not to a bare `throw` statement), or gate
141+
it behind a config flag — never silently opt production code into new
142+
exceptions.
134143
- Not a replacement for `OWN002`/`OWN003` (compile-time, zero runtime cost,
135144
works before the code ever ships) or for OwnAudit phase 5 (ground-truth heap
136145
retention) — it fills the gap between them: cases neither can see, at the

0 commit comments

Comments
 (0)