Skip to content

fix(extractor): oracle-driven FP hardening — 3 precision fixes + FP baseline#156

Merged
PhysShell merged 16 commits into
mainfrom
claude/mos-ownership-summary-n3q3j4
Jun 28, 2026
Merged

fix(extractor): oracle-driven FP hardening — 3 precision fixes + FP baseline#156
PhysShell merged 16 commits into
mainfrom
claude/mos-ownership-summary-n3q3j4

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Прогнал кросс-тул оракул (Own.NET vs CodeQL vs Infer#) на 5 топ-NuGet библиотеках, оттриажил все own-only находки против реального исходника и устранил ложные срабатывания: 3 точечных фикса экстрактора закрывают целые классы FP прямо в коде (каждый подтверждён повторным прогоном оракула на реальном репо), а проверенные-но-непочинибельные FP уезжают в именованный baseline, чтобы повторные прогоны показывали только новое. Recall-сторону тоже проверил — настоящих пропущенных продакшн-течей не нашлось (oracle-only = CodeQL over-reporting на escape/no-op + sample-код).

Тип изменения

  • feat — новая возможность
  • fix — исправление бага
  • docs — документация
  • refactor / chore / test / ci — без изменения поведения

Как проверено

  • python tests/run_tests.py (зелёный; corpus 27/27 reductions)
  • ruff check . и mypy (в CI: lint job — pass)
  • селфтесты затронутых скриптов (python scripts/oracle_compare.py --selftest 30/30, python scripts/benchmark.py --selftest)

C# проверяется в CI (локально dotnet нет): golden C#, leak extractor, corpus benchmark 32/33 caught · 34/34 fixes clean · 0 FP. Сверх того, каждый из 3 фиксов подтверждён живым прогоном оракула на реальном коде: NLog own-only 8 → 3, protobuf own-only → 0.

Связанные issue

Нет. Продолжение oracle-методологии (docs/notes/oracle.md).

Чеклист

  • изменение покрыто тестом/селфтестом — 3 корпусные фикстуры + селфтесты компаратора
  • README/docs обновлены при необходимости — docs/notes/oracle-known-fps.md (новый), oracle.md
  • коммиты в conventional-commit стиле (fix:/oracle: scoped)

Подробнее

3 фикса экстрактора (precision, все аддитивны — recall защищён floor'ом)

  1. CallReleasesReceiver — поле, освобождаемое через first-party extension-метод, дизпозящий свой ресивер (NLog WaitForDispose(this Timer)), переиспользуя ConsumesParam. Чистит 4 NLog timer FP (прямой/alias-ресивер).
  2. RefExchangeNulledField — teardown-идиома var t = Interlocked.Exchange(ref _f, null); t?.Dispose() (NLog TimeoutContinuation). Закрывает 5-й таймер → NLog FP baseline 5 → 0, own-only 8 → 3 (остаток — настоящие benign-поля, оставлены видимыми).
  3. PropertyReturnsOwnedMember — self-cycle подписки через get-only property над owned-полем (this.OwnedChild.Event += handler; protobuf XsltOptions). get-only обязательно (settable можно подменить на injected). protobuf own-only → 0.

Оракул-инфраструктура (scripts/oracle_compare.py)

  • --baseline: allowlist проверенных FP, матчинг по (repo, basename, OWN-code, msg-substring)не по строке (переживает дрейф HEAD), отдельная секция «Known FP (baselined)».
  • _is_test_path: ловит camelCase *UnitTests* проекты.
  • corpus/oracle-fp-baseline.txt: 7 правил, у каждого корневая причина.

Документация

docs/notes/oracle-known-fps.md — полный триаж 20 own-only находок, 4 корневые причины, что починено в коде vs что осталось честным warning/baseline (напр. Newtonsoft serializer.Error: source уезжает, доказать non-leak нельзя без lifetime-моделирования).

Что НЕ делалось (осознанно)

  • Newtonsoft serializer.Error, _textWriter, CsvHelper process-lived host — остаются в baseline как честные warning / непочинибельные без отдельной фичи.
  • Recall (oracle-only) — проверен, реальных продакшн-гэпов нет; гнаться за ним = делать НАС овер-репортером.

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added an optional “Known FP (baselined)” section to oracle comparison results.
    • Introduced an oracle baseline file mechanism to suppress known false-positive matches during triage.
  • Bug Fixes

    • Improved detection of safe event-subscription lifetimes for get-only sources that return owned members.
    • Enhanced disposable-release recognition for delegated cleanup via first-party extension methods, including atomic exchange-then-null teardown patterns.
    • Refined timer teardown handling to reduce noisy owned-field leak reports.
  • Documentation

    • Updated oracle documentation and known false-positive notes to reflect the new baselining workflow and reporting buckets.

claude added 15 commits June 27, 2026 12:05
… for main)

The oracle workflow_dispatch is blocked for the automation token (403). Point the
existing dev-only push fallback at this branch so a corpus/oracle-target.txt bump
runs the cross-tool oracle. Dev-loop only — not to be merged to main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
Remove the claude/mos-ownership-summary branch from the oracle.yml push
filter and restore corpus/oracle-target.txt to the systemevents-console
fixture. These were temporary edits to dispatch the cross-tool oracle on
five NuGet targets (Newtonsoft.Json, CsvHelper, serilog, NLog, protobuf-net);
the runs are done, so the dev scaffolding comes back out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
Triaged all 20 own-only findings from the 2026-06-27 cross-tool oracle run
(Newtonsoft.Json, CsvHelper, serilog, NLog, protobuf-net) against each
target's real source. 12 are false positives, 4 are true-but-some-benign
catches the oracle leak query can't express (kept visible), 2 are test/sample
noise. None came from the new owned-API recognition — it added no noise on
third-party code.

To keep re-runs actionable:
- oracle_compare.py gains --baseline: an allowlist of verified FPs, matched by
  (repo, basename, OWN code, message-substring) NOT line number (targets drift
  at HEAD), moved into a separate 'Known FP (baselined)' section so the triage
  queue surfaces only new findings. Selftest covers the loader, name-not-line
  match key, repo scoping, '*' wildcard, and render (30/30).
- _is_test_path gains a safe 'unittest' substring rule so camelCase test
  projects (protobuf's BuildToolsUnitTests) are excluded as non-product.
- corpus/oracle-fp-baseline.txt seeds 13 rules (covering 14 findings) with a
  per-entry root cause; the workflow passes it automatically.
- docs/notes/oracle-known-fps.md records every verdict and the four analyzer
  limitations behind the FPs, each naming the fix that would retire its entry.
  True positives (serilog CTS, NLog benign field leaks) are deliberately not
  suppressed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
A field released through a 'drain and dispose' sink — a first-party extension
method that disposes its receiver, like NLog's WaitForDispose(this Timer,
TimeSpan) — was reported as an OWN001 owned-field leak, because the disposal
scan only keys on a literal .Dispose()/.Close()/.DisposeAsync() on the field.

Credit field.M(...) as a release when M is a first-party extension method whose
receiver it disposes. CallReleasesReceiver reuses the existing ConsumesParam on
the reduced extension method's receiver parameter, so the release is proved by
inspecting M's real body (following first-party forwarding, cycle-guarded,
IDisposable-only) — never guessed from the name. Scope is extension methods
only; an instance method disposing its own 'this' is left out.

Clears the NLog timer FPs where the sink is called on the field directly or
through a 'var t = _field;' alias (AsyncTaskTarget, AsyncTargetWrapper,
BufferingTargetWrapper). TimeoutContinuation, which disposes the result of
Interlocked.Exchange(ref _timer, null), stays baselined until ref-exchange
alias tracking lands.

New corpus fixture field-dispose-via-helper proves it: before.cs leaks the
Timer field (OWN001 caught), after.cs releases it via the sink and must be
silent — the benchmark's after-clean gate is the end-to-end check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
…er FPs

Temporary, dev-only: point the push-triggered oracle at NLog/NLog and disable
the four direct/alias timer baseline entries that the CallReleasesReceiver fix
(6f4818c) should now clear. If they stay out of own-only on the live run, the
fix cleared them for real and the entries are deleted; if they resurface, they
are restored. TimeoutContinuation stays baselined — its Interlocked.Exchange
ref-alias is out of the fix's scope. Reverted after the run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
…line

Live NLog oracle re-run with the four WaitForDispose timer entries disabled
confirms the CallReleasesReceiver fix cleared them at the source: leak-class
own-only total 8 -> 4, and the four timers (AsyncTaskTarget _taskTimeoutTimer/
_lazyWriterTimer, AsyncTargetWrapper _lazyWriterTimer, BufferingTargetWrapper
_flushTimer) appear in neither own-only nor the baselined section. The remaining
NLog own-only is just the three benign-but-real undisposed fields (XmlParser
_xmlSource, FileTarget _reusable*Stream), with only TimeoutContinuation
_timeoutTimer baselined (its Interlocked.Exchange ref-alias is out of scope).

- Delete the four now-redundant baseline entries (baseline: 13 -> 9 rules).
- Document the fix landed in oracle-known-fps.md (root-cause #1 mostly-fixed).
- Restore the dev scaffolding: oracle-target.txt back to the systemevents-console
  fixture, remove the branch from the oracle.yml push filter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
Clears the last NLog WaitForDispose timer FP — TimeoutContinuation, which
releases its timer via the atomic detach-and-dispose idiom:

    var current = Interlocked.Exchange(ref _timeoutTimer, null);
    current?.WaitForDispose(TimeSpan.Zero);

Exchange atomically nulls the field and RETURNS the object it used to own, so
the local aliases the field's just-detached disposable. RefExchangeNulledField
binds such a local to the field in aliasToField (alongside the plain
'var x = _field;' alias), so the existing null-conditional + CallReleasesReceiver
scan credits the field as released. Restricted to a null/default replacement —
the unambiguous teardown; an exchange installing a new non-null value re-arms the
field with an object the syntactic scan can't follow, so it is declined
(precision-first: never hide a real leak).

New corpus fixture field-dispose-via-exchange (faithful to TimeoutContinuation)
proves it: before.cs leaks (OWN001), after.cs releases via the idiom and is
silent. All five NLog timer FPs are now fixed at the source; the NLog FP baseline
is empty. Re-pointing the oracle at NLog to confirm own-only 4 -> 3 on real code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
…re scaffolding

Live NLog oracle re-run confirms the Interlocked.Exchange teardown fix clears the
last timer FP: Own.NET leak-class total 4 -> 3, own-only is now just the three
genuine benign-but-real undisposed fields (XmlParser _xmlSource, FileTarget
_reusable*Stream), TimeoutContinuation _timeoutTimer absent from both own-only and
the baselined section, and the report has no Known-FP section at all (NLog baseline
empty). Restore the dev scaffolding: oracle-target.txt back to the systemevents-console
fixture, remove the branch from the oracle.yml push filter.

Full arc: NLog FP baseline 5 -> 0, own-only 8 -> 3, every WaitForDispose timer FP
closed at the source (4 by CallReleasesReceiver, 1 by RefExchangeNulledField).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
A subscription whose event SOURCE is reached through a get-only property the class
owns — this.Child.Event += this-capturing-handler, where Child is a property over a
constructed field — is the same collectable self-cycle as subscribing on the owned
field directly, not a leak. The self-owned exemption already covered this/fields/
locals the class constructs but not the PROPERTY receiver, so protobuf-net's
CommandLineOptions (XsltOptions, a get-only property over a constructed
XsltArgumentList, with a this-capturing handler) was falsely warned as an injected
subscription leak.

PropertyReturnsOwnedMember recognises a get-only property whose value the class
owns: an auto-property '{ get; } = new T()', or a getter returning a constructed
member (=> _owned / get => _owned / get { return _owned; }). Get-only is required —
a settable property could be reassigned to an injected, longer-lived object, which
we cannot prove bounded, so it falls through to the honest 'injected' warning
(precision-first: never silently drop a real leak).

New corpus fixture subscription-self-owned-property: before.cs subscribes to an
injected bus (real OWN001 leak, caught), after.cs to a self-owned property (silent).

Newtonsoft's serializer.Error (serializer = Create() that escapes, handler is a
param's delegate) stays baselined: proving it non-leak needs real lifetime
modelling, so the 'may outlive' warning is honest, not a clear FP.

Re-pointing the oracle at protobuf to confirm the XsltOptions FP clears.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
…restore scaffolding

Live protobuf oracle re-run confirms PropertyReturnsOwnedMember clears the
CommandLineOptions XsltMessageEncountered self-cycle FP at the source: own-only 0,
the finding absent from both own-only and the baselined section. Delete its baseline
entry (protobuf baseline 5 -> 4 findings) and document the fix landed (root-cause #3
PARTLY FIXED). Restore the dev scaffolding: oracle-target.txt back to the
systemevents-console fixture, remove the branch from the oracle.yml push filter.

Newtonsoft serializer.Error stays baselined as an honest 'may outlive' warning (its
source escapes and the handler is a parameter's delegate — non-leak is unprovable
without lifetime modelling).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a96201b2-5e32-49c5-a279-4a4675197a8e

📥 Commits

Reviewing files that changed from the base of the PR and between edbf203 and 74fcbc1.

📒 Files selected for processing (3)
  • corpus/real-world/field-dispose-via-helper/notes.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • scripts/oracle_compare.py
✅ Files skipped from review due to trivial changes (1)
  • corpus/real-world/field-dispose-via-helper/notes.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • scripts/oracle_compare.py

📝 Walkthrough

Walkthrough

Adds extractor rules for owned get-only properties, extension-method disposal, and Interlocked.Exchange aliasing; adds three corpus cases for those patterns; and introduces oracle baselining with CI wiring, docs, and confirmed false-positive entries.

Changes

Extractor: New Disposal & Subscription Recognition Rules

Layer / File(s) Summary
Self-owned property subscription exemption
frontend/roslyn/OwnSharp.Extractor/Program.cs
IsSelfOwnedSource now treats certain get-only properties as self-owned when PropertyReturnsOwnedMember proves they return an owned member of the analyzed class.
CallReleasesReceiver and RefExchangeNulledField helpers
frontend/roslyn/OwnSharp.Extractor/Program.cs
CallReleasesReceiver credits receiver disposal through first-party extension methods, and RefExchangeNulledField maps Interlocked.Exchange(ref _field, null/default) results back to the field for release tracking.

Corpus Fixtures for New Patterns

Layer / File(s) Summary
field-dispose-via-helper corpus case
corpus/real-world/field-dispose-via-helper/*
Before/after C# files, OWN model, expected OWN001, and notes cover the drain-and-dispose timer helper pattern.
field-dispose-via-exchange corpus case
corpus/real-world/field-dispose-via-exchange/*
Before/after C# files, OWN model, expected OWN001, and notes cover the Interlocked.Exchange detach-and-dispose timer pattern.
subscription-self-owned-property corpus case
corpus/real-world/subscription-self-owned-property/*
Before/after C# files, OWN model, expected OWN001, and notes distinguish injected-bus subscription leaks from owned-property self-cycles.

Oracle FP Baseline Workflow

Layer / File(s) Summary
BaselineRule, apply_baseline, and render_md update
scripts/oracle_compare.py
Adds baseline parsing/application, a baselined result bucket, markdown/JSON output for known false positives, test-path classification changes, and selftests for the new behavior.
CI oracle step and baseline file
.github/workflows/oracle.yml, corpus/oracle-fp-baseline.txt
The workflow conditionally passes --baseline, and the baseline file lists confirmed false positives for NLog, protobuf-net, Newtonsoft.Json, and CsvHelper.
Oracle docs and triage notes
docs/notes/oracle.md, docs/notes/oracle-known-fps.md
The oracle docs describe the baselined bucket, and the triage note records the 2026-06-27 verdicts, categories, and baseline rules.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PhysShell/Own.NET#21: Direct code-level overlap in scripts/oracle_compare.py and oracle.yml around oracle comparison and reporting.
  • PhysShell/Own.NET#26: Also changes scripts/oracle_compare.py/.github/workflows/oracle.yml output and CLI wiring for oracle comparisons.
  • PhysShell/Own.NET#13: Related extractor work around the self-owned-source event-subscription logic in Program.cs.

Poem

🐇 I hopped through timers, safe and neat,
Exchange and dispose — a tidy feat.
The oracle murmurs, “those old fakes stay put,”
While fresh own-only tracks get the boot.
My whiskers twitch: the baseline sings 🎶

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: extractor FP hardening plus an oracle baseline.
Description check ✅ Passed The description follows the template and includes purpose, change type, verification, related issue, and checklist.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/mos-ownership-summary-n3q3j4

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: edbf203184

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +495 to +496
return recv is IPropertySymbol { IsStatic: false } p
&& PropertyReturnsOwnedMember(p, owned, model);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate owned-property exemption on the receiver

When the subscription source is reached through another instance, e.g. other.Channel.Changed += ..., recv still resolves to the get-only Channel property and PropertyReturnsOwnedMember can return true because that property owns other's field. That suppresses a real leak: other.Channel may outlive this subscriber and retain the handler. Please only apply this property exemption for bare/this property accesses, or otherwise prove the property receiver is self-owned.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@corpus/real-world/field-dispose-via-helper/notes.md`:
- Around line 34-40: Update the caveat in the notes to reflect the new
ref-exchange alias tracking support: the current text in the notes section still
says the `Interlocked.Exchange(ref _timer, null)` teardown is not tracked and
remains in the oracle FP baseline until tracking lands. Revise that wording to
match the new behavior introduced by the `field-dispose-via-exchange` fixture
and the `TimeoutContinuation`/`Interlocked.Exchange` path, removing the obsolete
limitation and keeping the scope note accurate.

In `@frontend/roslyn/OwnSharp.Extractor/Program.cs`:
- Around line 529-532: The self-ownership check in Program.cs is using rsym.Name
in the returned-member test, which can incorrectly match an unrelated field or
property with the same name. Update the logic in the symbol analysis around
returned/owned members to compare the actual IFieldSymbol or IPropertySymbol
against the owned members of the analyzed containing type, rather than using
names. Use the existing symbols rsym, pm, and the owning type context in the
extractor flow to ensure only members declared on the analyzed type can satisfy
the ownership check.
- Around line 2582-2586: The argument check in the Interlocked.Exchange pattern
matcher only handles the `default` literal, so `default(T)` is missed. Update
the condition in `Program`’s matching logic to also accept
`DefaultExpressionSyntax` alongside `SyntaxKind.DefaultLiteralExpression`, while
keeping the existing `ref` and null/default handling intact.

In `@scripts/oracle_compare.py`:
- Around line 252-269: The _load_baseline parser currently accepts empty message
substrings, which makes BaselineRule matching behave like a wildcard because an
empty string matches every message. Update the parsing logic in _load_baseline
so malformed or empty substrings are rejected before creating a BaselineRule,
and only append rules when the message-substring field is non-empty after
trimming.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bcde37f5-4ca9-470c-9802-f0b6a1b4ccc8

📥 Commits

Reviewing files that changed from the base of the PR and between 5e7bb5c and edbf203.

📒 Files selected for processing (21)
  • .github/workflows/oracle.yml
  • corpus/oracle-fp-baseline.txt
  • corpus/real-world/field-dispose-via-exchange/after.cs
  • corpus/real-world/field-dispose-via-exchange/before.cs
  • corpus/real-world/field-dispose-via-exchange/case.own
  • corpus/real-world/field-dispose-via-exchange/expected-diagnostics.txt
  • corpus/real-world/field-dispose-via-exchange/notes.md
  • corpus/real-world/field-dispose-via-helper/after.cs
  • corpus/real-world/field-dispose-via-helper/before.cs
  • corpus/real-world/field-dispose-via-helper/case.own
  • corpus/real-world/field-dispose-via-helper/expected-diagnostics.txt
  • corpus/real-world/field-dispose-via-helper/notes.md
  • corpus/real-world/subscription-self-owned-property/after.cs
  • corpus/real-world/subscription-self-owned-property/before.cs
  • corpus/real-world/subscription-self-owned-property/case.own
  • corpus/real-world/subscription-self-owned-property/expected-diagnostics.txt
  • corpus/real-world/subscription-self-owned-property/notes.md
  • docs/notes/oracle-known-fps.md
  • docs/notes/oracle.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • scripts/oracle_compare.py

Comment thread corpus/real-world/field-dispose-via-helper/notes.md Outdated
Comment thread frontend/roslyn/OwnSharp.Extractor/Program.cs
Comment thread frontend/roslyn/OwnSharp.Extractor/Program.cs
Comment thread scripts/oracle_compare.py
)

Addresses the Codex/CodeRabbit review on #156:

- IsSelfOwnedSource: gate the get-only-property exemption to a THIS/bare access
  (`Channel` / `this.Channel`). `other.Channel.Event += h` reaches another
  instance's property, which may outlive this subscriber, so it stays flagged
  (Codex P2).
- PropertyReturnsOwnedMember: require the returned member's CONTAINING TYPE to be
  the analyzed class, not just a name match — a same-named field on another type
  no longer satisfies ownership (CodeRabbit, major).
- RefExchangeNulledField: also accept `default(T)` (DefaultExpressionSyntax), not
  only the `default` literal — `Interlocked.Exchange(ref _f, default(Timer))`
  was missed (CodeRabbit, minor).
- oracle_compare _load_baseline: reject an empty message-substring (it would make
  `substr in message` a wildcard a typo could turn into a blanket suppressor),
  and preserve `|` in the reason via join (CodeRabbit, major). Selftest extended
  (31 checks).
- field-dispose-via-helper/notes.md: drop the obsolete caveat — the
  Interlocked.Exchange teardown is now covered by the field-dispose-via-exchange
  fixture, not baselined (CodeRabbit, minor).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
@PhysShell PhysShell merged commit ede20ce into main Jun 28, 2026
30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants