Skip to content

Commit 47c46a6

Browse files
committed
oracle: Page.xaml.cs timer is a TRUE POSITIVE (custom IDisposable Stopwatch), not an FP
Cracked the mechanism behind issue #161 from the core side: the finding renders via the flow-locals path (ownir.py rkind=="flow-local"), whose extractor candidate gate REQUIRES ImplementsIDisposable. The raw protobuf source shows the file has no `using System.Diagnostics;` — it has `using Nuxleus.Performance;` and uses `Stopwatch.UnitPrecision` / `UnitPrecision.NANOSECONDS` / `timer.Scope = () => …`. So `Stopwatch` is `Nuxleus.Performance.Stopwatch`, a custom IDisposable scope-timer (you can only `using` an IDisposable), NOT System.Diagnostics.Stopwatch. `Stopwatch timer = new Stopwatch()` is genuinely never disposed — the disposing `using (timer) { … }` block is commented out — so the OWN001 is a REAL leak the extractor correctly flagged. It stays baselined as NON-PRODUCT sample (assorted/ Silverlight demo), like the NetTranscoder `sync` entry — reclassified from FP to true-but-benign-sample. The earlier "missed using" and "non-IDisposable Stopwatch FP" readings were both wrong; issue #161 is closed as invalid. Corrected the baseline entry reason, oracle-known-fps.md root-cause #1, the per-finding verdict row, and the disposition summary (Baselined FP 6→5, true-but-benign sample 2→3). oracle selftest 31/31. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
1 parent c415d64 commit 47c46a6

2 files changed

Lines changed: 30 additions & 21 deletions

File tree

‎corpus/oracle-fp-baseline.txt‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,16 @@ protobuf-net/protobuf-net | ProtoTranscoder.cs | OWN001 | 'sync' | non-product (
3737
# a get-only property over a constructed XsltArgumentList field, handler captures `this`).
3838
# Now fixed at the source by PropertyReturnsOwnedMember — confirmed cleared on a live
3939
# protobuf oracle run (own-only 0, the finding absent from own-only and baselined) — deleted.
40-
# RE-TRIAGED 2026-06-28 (live protobuf oracle run): the prior reason — "disposed by an enclosing
41-
# using (timer) the extractor missed" — was a MISDIAGNOSIS read from COMMENTED-OUT code. In the
42-
# real source the only `using (timer)` block is commented out, and `timer` is a Stopwatch (RunTest;
43-
# btnRunTest_Click has a DispatcherTimer local) — neither is IDisposable. So the using-statement
44-
# fix does NOT apply here (confirmed: still emitted, still baselined). It stays a non-product FP;
45-
# the open question is WHY a non-IDisposable timer-shaped local is flagged at all (needs a local
46-
# extractor repro; tracked in issue #161). See docs/notes/oracle-known-fps.md root-cause #1.
47-
protobuf-net/protobuf-net | Page.xaml.cs | OWN001 | local 'timer' | non-product (assorted/ Silverlight perf sample); `timer` is a non-IDisposable Stopwatch/DispatcherTimer and the only using(timer) block is commented out — not a missed-using; flagging it is a separate precision question, kept baselined
40+
# RE-TRIAGED 2026-06-28 (live protobuf oracle run + raw source). This is a TRUE POSITIVE, not an
41+
# FP: `Stopwatch` here is `Nuxleus.Performance.Stopwatch` (the file has `using Nuxleus.Performance;`
42+
# and uses `Stopwatch.UnitPrecision`/`UnitPrecision.NANOSECONDS`/`timer.Scope = () => …` — NOT
43+
# System.Diagnostics.Stopwatch), a custom IDisposable scope-timer. `Stopwatch timer = new Stopwatch()`
44+
# is genuinely never disposed: the disposing `using (timer) { … }` block is COMMENTED OUT. The
45+
# extractor flags it via the flow-locals path, which requires ImplementsIDisposable — so it
46+
# correctly resolved the custom type as IDisposable. Kept baselined as NON-PRODUCT sample (assorted/
47+
# Silverlight perf demo), exactly like the assorted/ NetTranscoder `sync` entry. The earlier
48+
# "missed using" and "non-IDisposable Stopwatch FP" readings were both wrong; issue #161 is closed.
49+
protobuf-net/protobuf-net | Page.xaml.cs | OWN001 | local 'timer' | non-product (assorted/ Silverlight perf demo); TRUE POSITIVE — `timer` is a custom IDisposable Nuxleus.Performance.Stopwatch genuinely never disposed (the using(timer) block is commented out). Baselined as non-product, not as an FP
4850

4951
# --- JamesNK/Newtonsoft.Json ----------------------------------------------------
5052
# STAYS BASELINED (soundness wall): JsonTextWriter is a THIRD-PARTY wrapper — the BCL pass-through

‎docs/notes/oracle-known-fps.md‎

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,16 @@ reason: we and the oracles occupy orthogonal niches.
2424
| disposition | count | what happens on re-run |
2525
|---|---:|---|
2626
| **Fixed in the extractor** | 6 | no longer fire (5 NLog `WaitForDispose` timers + protobuf `XsltOptions` self-cycle — see below) |
27-
| **Baselined FP** | 6 | moved to "Known FP (baselined)", out of the triage queue |
27+
| **Baselined FP** | 5 | moved to "Known FP (baselined)", out of the triage queue |
2828
| **Non-product (path filter)** | 2 | dropped by `--exclude-tests` (`unittest` rule) |
2929
| **True positive — kept visible** | 4 | stays in "Own.NET only" (real catch, oracle can't express) |
30-
| **True-but-benign — kept, baselined-as-sample** | 2 | (protobuf `assorted/` samples) baselined as non-product |
30+
| **True-but-benign — kept, baselined-as-sample** | 3 | (protobuf `assorted/` samples) baselined as non-product |
3131

32-
The 6 baselined FPs + the 2 non-product-sample reals = 8 findings, covered by
32+
(Re-triage 2026-06-28: `Page.xaml.cs` `timer` moved from "Baselined FP" 6→5 to
33+
"True-but-benign sample" 2→3 — it is a real leak of a custom `IDisposable`
34+
`Nuxleus.Performance.Stopwatch`, not the BCL non-disposable type first assumed.)
35+
36+
The 5 baselined FPs + the 3 non-product-sample reals = 8 findings, covered by
3337
**7 rules** in `corpus/oracle-fp-baseline.txt` (the two `NetTranscoder` copies
3438
share one basename-keyed rule); the 2 test-base findings are the `--exclude-tests`
3539
drops; the 4 true positives are deliberately **not** suppressed.
@@ -87,7 +91,7 @@ this.
8791
| `src/protobuf-net.Core/ProtoWriter.BufferWriter.cs` `_nullWriter` | **FP → baseline** | intentional null-object kept attached for pooled reuse; `Dispose()` comments *"don't cascade dispose to the null one"* |
8892
| `assorted/.../ProtoTranscoder.cs` `sync` (×2 copies) | **true-but-benign → baseline (non-product sample)** | `NetTranscoder` isn't `IDisposable`; one `ReaderWriterLockSlim` for app lifetime in a sample/extension tree |
8993
| `assorted/ProtoGen/CommandLineOptions.cs` `XsltMessageEncountered` | **FP → baseline** | self-subscription: publisher (`xsltOptions`) and the lambda are both owned by the same `CommandLineOptions`, co-lifetimed |
90-
| `assorted/SilverlightExtended/Page.xaml.cs` `timer` | **FP → baseline (re-triaged 2026-06-28)** | NOT a missed `using` — the `using (timer)` block is commented out and `timer` is a non-`IDisposable` `Stopwatch`/`DispatcherTimer`; flagging it is a separate precision question. Sample code. |
94+
| `assorted/SilverlightExtended/Page.xaml.cs` `timer` | **true-but-non-product → baseline (re-triaged 2026-06-28)** | TRUE POSITIVE: `timer` is a custom `IDisposable` `Nuxleus.Performance.Stopwatch` (NOT `System.Diagnostics.Stopwatch`) never disposed — the disposing `using (timer)` is commented out. Correctly flagged; baselined as non-product sample. |
9195
| `src/BuildToolsUnitTests/AnalyzerTestBase.cs` `logging.Log` | **test noise → path filter** | xUnit fixture; per-test lifetime |
9296
| `src/BuildToolsUnitTests/GeneratorTestBase.cs` `logging.Log` | **test noise → path filter** | xUnit fixture; per-test lifetime |
9397

@@ -148,15 +152,18 @@ entry and let the oracle re-confirm clean.
148152
release, mirroring the `MemoryPool` owner branch), with sound throw-routing
149153
(`onThrowDefinite`). Corpus fixtures: `local-dispose-via-using-statement`,
150154
`using-statement-throw-releases`. **It does NOT clear the protobuf `Page.xaml.cs`
151-
baseline entry, though** — a live protobuf oracle run (2026-06-28) showed that finding
152-
STILL emitted (still baselined), and re-triage of the real source found the prior
153-
reason was a **misdiagnosis read from commented-out code**: the only `using (timer)`
154-
block is commented out, and `timer` is a `Stopwatch` (`RunTest`) / a `DispatcherTimer`
155-
local (`btnRunTest_Click`) — neither is `IDisposable`. So the entry stays baselined as a
156-
non-product sample FP; the real open question is **why a non-`IDisposable` timer-shaped
157-
local is flagged at all** (a precision follow-up needing a local extractor repro, not a
158-
missed `using`; tracked in issue #161). Other custom-sink fixtures: `field-dispose-via-helper`,
159-
`field-dispose-via-exchange`.
155+
baseline entry — because that entry is a TRUE POSITIVE, not an FP.** A live protobuf
156+
oracle run (2026-06-28) + the raw source settled it: `Stopwatch` there is
157+
`Nuxleus.Performance.Stopwatch` (the file has `using Nuxleus.Performance;` and uses
158+
`Stopwatch.UnitPrecision` / `timer.Scope = () => …`, NOT `System.Diagnostics.Stopwatch`),
159+
a custom **`IDisposable`** scope-timer that is genuinely never disposed (the disposing
160+
`using (timer)` block is commented out). The extractor flags it via the flow-locals path,
161+
which gates on `ImplementsIDisposable` — so it correctly resolved the custom type and
162+
reported a real leak. It stays baselined as **non-product sample** (assorted/ Silverlight
163+
demo), like the `NetTranscoder` `sync` entry — not as an FP. (Two earlier readings —
164+
"missed `using`" and "non-`IDisposable` `Stopwatch` FP" — were both wrong; issue #161 was
165+
opened on the second and then closed as invalid.) Other custom-sink fixtures:
166+
`field-dispose-via-helper`, `field-dispose-via-exchange`.
160167

161168
2. **No-op `Dispose` not modelled — PARTLY FIXED** *(Newtonsoft `TraceJsonReader.
162169
_textWriter`).* We flag any undisposed `IDisposable` structurally, without modelling

0 commit comments

Comments
 (0)