Skip to content

fix: close 4 precision gaps from the oracle sweep (#220, #222, #223, #224)#231

Merged
PhysShell merged 3 commits into
mainfrom
claude/precision-quickwins-220-222-223-224
Jul 11, 2026
Merged

fix: close 4 precision gaps from the oracle sweep (#220, #222, #223, #224)#231
PhysShell merged 3 commits into
mainfrom
claude/precision-quickwins-220-222-223-224

Conversation

@PhysShell

@PhysShell PhysShell commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Закрывает 4 мелких precision-gap issue из oracle-sweep (#201) одним PR: #223 (allowlist для CommandManager.RequerySuggested), #220 (using (field = new T()) как release поля), #222 (self-owned-template-part exemption для локалов/pattern-var), #224 (self-detaching handler). Все четыре — точечные правки в экстракторе (frontend/roslyn/OwnSharp.Extractor/Program.cs), ядро (ownlang) не тронуто.

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

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

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

  • python tests/run_tests.py
  • ruff check . и mypy
  • селфтесты затронутых скриптов (python scripts/<...>.py --selftest)

В окружении временно установлен .NET 8 SDK (официальный dotnet-install.sh, сам SDK не коммитится) — экстрактор собран и прогнан локально по всем новым сэмплам плюс полному существующему набору (32 файла + новый OwnIgnoreSample.cs после ребейза на актуальный main). Точный текст обновлённого CI-шага «Check facts through the core» прогнан против реального вывода — все ассерты (старые и новые) проходят, exit 0. Вывод ниже.

Связанные issue

Closes #220. Closes #222. Closes #223. Closes #224.

Чеклист

  • изменение покрыто тестом/селфтестом (или объяснено, почему нет) — 4 новых сэмпла в frontend/roslyn/samples/, у каждого позитивный кейс (перестаёт флагаться) и хотя бы один негативный контроль (обязан остаться флагнутым), плюс ассерты в обе стороны в CI-джобе «C# leak extractor»
  • README/docs обновлены при необходимости — сами issues уже ссылаются на docs/notes/oracle-sweep-2026-07-10.md и field-notes-patterns.md (записи 14/16/17/18); отдельных правок документации в этом PR не требуется
  • коммиты в conventional-commit стиле (feat:, fix:, docs: …)

Детали по каждому фиксу

#223CommandManager.RequerySuggested allowlist. Ровно одна запись (IsWeakReferencedStaticEvent, namespace+type+name), без общего ослабления static-source тира. Сэмпл: RequerySuggestedAllowlistSample.cs — позитив (ImeSupportLike, молчит) + негатив (OrdinaryStaticSubscriber на НЕ-allowlisted статик-событии — по-прежнему OWN014).

#224 — self-detaching handler. Поиск -= в теле самого подписанного хендлера (тот же символ метода, то же имя события — не просто текстовое совпадение). Сэмпл: SelfDetachingHandlerSample.cs — позитив (DropDownButtonLike, молчит) + два негатива (не детачится вовсе; детачится, но от ДРУГОГО события — оба по-прежнему OWN001).

#220using (field = new T()). Расширение существующего using-lowering (using (existingLocal)) на acquisition-присваивание полю. Сэмпл: UsingFieldAcquisitionSample.cs — позитив (HashCheckerLike, молчит) + негатив (то же поле, то же присваивание, но БЕЗ using — по-прежнему OWN001).

#222 — template-part exemption для локалов/pattern-var. IsSelfOwnedSource уже принимал ILocalSymbol по имени — не хватало добавления имён template-part-локалов в selfOwned. Сэмпл: TemplatePartLocalCaptureSample.cs — два позитива (is T x через GetTemplateChild; обычный локал через Template.FindName, молчат) + негатив (локал-алиас ИНЖЕКТИРОВАННОГО поля, не template-fetch — по-прежнему OWN001).

Локальный прогон (dotnet 8.0.422, временный) — вывод обновлённого шага "Check facts through the core"
frontend/roslyn/samples/RequerySuggestedAllowlistSample.cs:62: error: [OWN014] event 'OtherStaticSource.SomethingChanged' is subscribed (handler 'handler') to a static (process-lived) event source that outlives 'OrdinaryStaticSubscriber'; ...
frontend/roslyn/samples/SelfDetachingHandlerSample.cs:46: warning: [OWN001] event 'content.Closed' is subscribed (handler 'OnClosed') but never unsubscribed ... 'NonDetachingSubscriber' ...
frontend/roslyn/samples/SelfDetachingHandlerSample.cs:64: warning: [OWN001] event 'content.Closed' is subscribed (handler 'OnClosed') but never unsubscribed ... 'WrongEventDetachSubscriber' ...
frontend/roslyn/samples/TemplatePartLocalCaptureSample.cs:59: warning: [OWN001] event 'local.Click' is subscribed (handler 'OnClick') but never unsubscribed ... 'InjectedLocalSubscriber' ...
frontend/roslyn/samples/UsingFieldAcquisitionSample.cs:35: error: [OWN001] IDisposable field 'cts' (type 'CancellationTokenSource?') is never disposed ... 'LeakyAssignerLike' ...

60 findings, 1 suppressed ([OwnIgnore]).
OK: real C# -> facts -> OWN001 (...) + OWN014 (...) + DI001..DI005 (...) + [OwnIgnore] suppression (...) at the C# location

Ни ImeSupportLike, DropDownButtonLike, HashCheckerLike, MetroWindowLike, OverloadViewerLike в выводе не появляются (молчат, как и требуется) — а весь пред-существующий 32-файловый (33 после мержа с OwnIgnoreSample.cs) регресс-набор проходит без изменений.


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved leak detection accuracy for weak static events and self-unsubscribing event handlers.
    • Correctly recognizes disposable fields acquired within using blocks.
    • Refined template-part handling to avoid false warnings while still flagging unrelated injected objects.
    • Ensured similarly named locals in different scopes are analyzed independently.
  • Tests

    • Added coverage for weak-event allowlists, self-detaching handlers, field disposal, and template-part captures.

…224)

Four small, independently-verified extractor fixes surfaced by the issue
positive sample (silenced) and at least one adversarial negative control
(still flagged) so precision on the general case can't regress silently:

- #223: a curated allowlist (exactly one entry) for
  System.Windows.Input.CommandManager.RequerySuggested, a WPF static event
  implemented over weak references — a subscriber is never pinned by it, so
  never detaching is the normal, safe idiom. Every other static-source
  subscription still raises OWN014 unchanged.
- #224: a subscribed handler that unsubscribes ITSELF inside its own body
  (a self-detaching one-shot handler) is now recognised as bounded — no
  external `-=` required. Matched by the handler's own method symbol and
  the subscribed event's name, so a same-named handler on an unrelated
  subscription, or a self-detach against the wrong event, cannot falsely
  suppress a real leak.
- #220: `using (field = new T())` — the field IS the `using` acquisition
  target — is now recognised as releasing the field at scope exit, closing
  the sibling gap next to the already-handled `using (existingLocal)` local
  form. A field assigned the same way but outside any `using` still leaks.
- #222: the self-owned-template-part exemption (GetTemplateChild/
  Template.FindName) now covers a template part captured as a plain local
  variable or an `is T x` pattern variable, not only a field assignment.
  IsSelfOwnedSource already accepted a local receiver by name — it only
  needed template-part local names folded into the self-owned set.

Every fix is scoped to the extractor (frontend/roslyn/OwnSharp.Extractor);
the Python core and OwnIR schema are untouched, no new fact fields, no
OWNIR_VERSION bump. Verified locally end-to-end (facts extracted with a
temporary .NET 8 SDK install, run through the exact updated CI script) —
the 4 positive samples are silent, all 6 negative controls (including the
pre-existing 32-sample regression suite) still raise their findings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D6Naf8CcezjCbikueKdeYv
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PhysShell, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 14 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d058001b-72e3-459d-b899-facca515d24c

📥 Commits

Reviewing files that changed from the base of the PR and between 4da5026 and cd1c0ee.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
📝 Walkthrough

Walkthrough

The Roslyn extractor now recognizes weak static events, self-detaching handlers, using-scoped field disposal, and template-part locals. New samples and CI assertions validate positive and negative classifications for these patterns.

Changes

Leak detection precision

Layer / File(s) Summary
Event release and weak-event handling
frontend/roslyn/OwnSharp.Extractor/Program.cs, frontend/roslyn/samples/RequerySuggestedAllowlistSample.cs, frontend/roslyn/samples/SelfDetachingHandlerSample.cs
Self-detaching handlers are treated as released when they detach from their sender, and CommandManager.RequerySuggested is excluded from static-event leak reporting.
Using-scoped field disposal
frontend/roslyn/OwnSharp.Extractor/Program.cs, frontend/roslyn/samples/UsingFieldAcquisitionSample.cs
Fields assigned inside using acquisitions are recorded as disposed, while equivalent assignments outside using remain leak cases.
Template-part local ownership
frontend/roslyn/OwnSharp.Extractor/Program.cs, frontend/roslyn/samples/TemplatePartLocalCaptureSample.cs
Template-part locals and pattern variables are tracked by symbol, preserving warnings for injected locals and same-named locals in other methods.
Extractor inputs and fact assertions
.github/workflows/ci.yml
The workflow processes four new samples and checks the expected OWN001 and OWN014 findings.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CIWorkflow
  participant OwnSharpExtractor
  participant OwnIR
  participant AssertionScript
  CIWorkflow->>OwnSharpExtractor: process Roslyn sample inputs
  OwnSharpExtractor->>OwnIR: emit ownership and release facts
  AssertionScript->>OwnIR: query expected OWN001 and OWN014 findings
  OwnIR-->>AssertionScript: return matching facts
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the four precision-gap fixes.
Description check ✅ Passed The description fills all required template sections and includes concrete details for changes, testing, issues, and checklist.
Linked Issues check ✅ Passed The PR implements the requested fixes and test coverage for issues #220, #222, #223, and #224.
Out of Scope Changes check ✅ Passed The changes stay within the extractor, new samples, and CI assertions, with no unrelated scope visible.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/precision-quickwins-220-222-223-224

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

public OrdinaryStaticSubscriber()
{
handler = OnSomethingChanged;
OtherStaticSource.SomethingChanged += handler;
public NonDetachingSubscriber(PopupLike content)
{
this.content = content;
content.Closed += OnClosed;
public WrongEventDetachSubscriber(PopupLike content)
{
this.content = content;
content.Closed += OnClosed;
Comment thread frontend/roslyn/samples/TemplatePartLocalCaptureSample.cs Fixed
// "any field assignment from `new` is a release."
public sealed class LeakyAssignerLike
{
private CancellationTokenSource? cts;

@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: 412ea0e311

ℹ️ 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 +3797 to +3801
foreach (var declr in cls.DescendantNodes().OfType<VariableDeclaratorSyntax>())
if (declr.Initializer?.Value is { } declInit
&& IsTemplatePartFetch(declInit)
&& model.GetDeclaredSymbol(declr) is ILocalSymbol declLocal)
selfOwned.Add(declLocal.Name);

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 Track template-part locals by symbol, not name

When a class contains a template-fetch local named, for example, button, this adds only that name to a class-wide set. Any later ILocalSymbol named button in another method/scope then satisfies IsSelfOwnedSource via owned.Contains(recv.Name), so an injected publisher aliased with the same local name is treated as self-owned and skipped instead of reporting OWN001. Scope the exemption to the actual local/pattern symbol rather than the reusable identifier.

Useful? React with 👍 / 👎.

Comment on lines +716 to +720
|| innerLeft.Name.Identifier.Text != ev.Name)
continue;
var innerHandlerSym = bodyModel.GetSymbolInfo(NormalizeHandler(inner.Right)).Symbol;
if (SymbolEqualityComparer.Default.Equals(innerHandlerSym, handlerMethod))
return true;

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 Require self-detach to target the subscribed source

For a handler subscribed as content.Closed += OnClosed, a body that executes other.Closed -= OnClosed returns true here because only the event name and handler method are compared. The original publisher still holds the delegate in that scenario, but the emitted fact is marked released, hiding the OWN001/OWN014 finding for any different source exposing the same event name.

Useful? React with 👍 / 👎.

… checks

Two real precision bugs caught by Codex's review of PR #231, both fixed
and covered by a dedicated regression sample:

- Template-part locals (issue #222) were folded into `selfOwned` by NAME,
  the same set used for fields. Field names are unique per class, so
  name-based lookup was sound there; a LOCAL's name is only unique within
  its own method, so an unrelated same-named local aliasing an injected
  source in a different method could wrongly borrow the exemption. Fixed
  by tracking template-part locals in a separate symbol-keyed set
  (`selfOwnedLocals`, SymbolEqualityComparer) that IsSelfOwnedSource checks
  by SYMBOL, never by name — fields are unaffected.
- HandlerSelfDetaches (issue #224) only compared the event NAME and the
  handler METHOD symbol, never the `-=`'s RECEIVER — so a handler that
  unsubscribed an unrelated object exposing an event of the same name
  would be wrongly credited as self-detaching, hiding a real leak on the
  actual subscribed source. Fixed by requiring the `-=`'s receiver (after
  stripping cast/`as`/parens/`!`) to resolve to the handler's own `sender`
  parameter — the only expression inside the handler provably referring
  to the actual runtime event source.

Also fixes an infinite loop in the new `UnwrapToBase` helper introduced
for the second fix: the default arm of the unwrap switch reassigned
`e = e` and never returned, so any handler body containing a cast/`as`/
parens/`!` expression that bottomed out at a plain identifier (i.e. every
realistic case) hung the extractor forever. Caught locally before push by
actually running the extractor against the sample fixtures — a `timeout`-
wrapped, single-file bisection isolated it to this exact function.

Extends the same 2 sample files with regression controls reproducing each
bug directly (a same-named unrelated local in a different method; a
self-detach off an unrelated receiver instead of `sender`), wired into
the CI assertions alongside the existing ones. Re-verified end to end
with the local .NET 8 SDK: full sample suite (35 files) + updated CI
script, exit 0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D6Naf8CcezjCbikueKdeYv
{
this.content = content;
this.other = other;
content.Closed += OnClosed;
public void Wire()
{
ButtonStub local = externalButton; // aliases an INJECTED field, not a template fetch
local.Click += OnClick;
public void WireInjected()
{
ButtonStub sameName = injectedButton;
sameName.Click += OnInjectedClick;
Same-tail collision as 2ad7dc9: main's #218 rotation block (Program.cs
released-disjunct + ci.yml sample/assertions) and this branch's four
quick-win blocks appended to the same places. Kept both: both released
disjuncts (self-detach #224 + rotation #218), both sample lists, main's
merged OK line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsWw4Ay8KLTHFom1HvRu3U
@PhysShell PhysShell merged commit 3ca4bde into main Jul 11, 2026
37 checks passed
PhysShell pushed a commit that referenced this pull request Jul 12, 2026
Lines starting with "#201)" / "#230/#231" after a soft wrap read as
malformed ATX headings to markdownlint. No content change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D6Naf8CcezjCbikueKdeYv
PhysShell added a commit that referenced this pull request Jul 12, 2026
docs: verify #230/#231 precision-gap fixes against 2026-07-10 oracle sweep
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants