|
| 1 | +# using-statement-throw-releases |
| 2 | + |
| 3 | +The sound handling of an **explicit `throw` inside a `using (existingLocal)` body** — the |
| 4 | +follow-up to [`local-dispose-via-using-statement`](../local-dispose-via-using-statement/notes.md), |
| 5 | +which first deferred this case (Codex P2 on PR #159). |
| 6 | + |
| 7 | +When the `using`-local release was threaded as a non-null `bodyOnThrow`, the |
| 8 | +`ThrowStatementSyntax` lowering bailed the **whole method** (it refuses a non-null `onThrow` |
| 9 | +because a throw inside a `try` might be caught). That lost detection of UNRELATED leaks in |
| 10 | +any method with such a body. This fixture pins the fixed behaviour. |
| 11 | + |
| 12 | +- **before.cs** — an unrelated local `conn` is never disposed → `OWN001`. Crucially, the |
| 13 | + `using (guard)` body contains `if (bad) throw …;`. The bug is only caught if the throw |
| 14 | + does **not** bail the method — so this fixture is a direct regression test for the |
| 15 | + no-bail fix. `guard` is released on every path (normal, throw, return) and stays silent. |
| 16 | +- **after.cs** — `conn` is `using`-declared (auto-disposed); the body still throws, and the |
| 17 | + method stays analysed and **clean**. |
| 18 | + |
| 19 | +## Recognition rule |
| 20 | + |
| 21 | +`LowerFlowStmt`/`LowerFlowStatements`/`LowerSwitchSection` thread an `onThrowDefinite` flag |
| 22 | +beside `onThrow`. It is true when a throw that runs `onThrow` **definitely** leaves the |
| 23 | +method uncaught — a `using` or a finally-only `try` (neither has a catch) — and false for a |
| 24 | +`try` with any `catch` (the throw may be caught). The `ThrowStatementSyntax` case: |
| 25 | + |
| 26 | +- `onThrow is null` (method level) → bare exit, as before; |
| 27 | +- `onThrow` non-null **and** `onThrowDefinite` → route the throw through `onThrow` (the |
| 28 | + release continuation, ending in exit), so the cleanup's resource is released on the throw |
| 29 | + path and the method stays analysed; |
| 30 | +- `onThrow` non-null and **not** definite (try-with-catch) → still bail. |
| 31 | + |
| 32 | +## Honesty caveat |
| 33 | + |
| 34 | +The definite routing covers `using` statements and finally-only `try`s. A `try` with a |
| 35 | +`catch` still bails an explicit throw in its body — modelling that soundly needs matching |
| 36 | +the thrown type against each catch clause, which the lowering does not do. That is the same |
| 37 | +conservative posture as before; this change only *adds* the provably-uncaught cases. |
0 commit comments