Skip to content

docs: record static-class subscriber exemption as a rejected approach#158

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

docs: record static-class subscriber exemption as a rejected approach#158
PhysShell merged 4 commits into
mainfrom
claude/mos-ownership-summary-n3q3j4

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Фиксируем в документации и в коде, почему расширение OWN014-исключения до || clsIsStatic (статик-класс как подписчик) — неверный путь, чтобы его не повторили. Подход пробовали в #157, отловили в ревью (Codex P2 + CodeRabbit Major) и откатили (488d505) ещё до мержа; чистый diff этой ветки = только документация + предупреждающий комментарий.

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

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

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

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

Изменения только документационные (без изменения поведения экстрактора): чистый diff vs main — 58 строк в docs/notes/oracle-known-fps.md и комментарий в Program.cs. Селфтесты прогнаны для контроля, что ничего не задето.

Связанные issue

Refs #157 (откачено в 488d505). Самостоятельной issue нет.

Чеклист

  • изменение покрыто тестом/селфтестом (или объяснено, почему нет) — чисто docs + коммент, поведение не меняется; селфтесты зелёные
  • README/docs обновлены при необходимости — это и есть docs-апдейт
  • коммиты в conventional-commit стиле (feat:, fix:, docs: …)

Что внутри

  • Program.csANTI-PATTERN комментарий на месте исключения clsIsApp: не расширять до || clsIsStatic. Статик-класс убирает только инстансный this, но лямбда-обработчик всё ещё захватывает локал, который process-lived событие пинит на весь процесс → реальная captured-local утечка, которую исключение молча проглотило бы.
  • oracle-known-fps.md — новая секция «Rejected approaches» с полным разбором: почему clsIsApp безопасен, а clsIsStatic — нет; почему даже capture-gated версия не закрыла бы CsvHelper ConsoleHost (он захватывает cts/resetEvent), поэтому те находки остаются в baseline.

🤖 Generated with Claude Code

https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm


Generated by Claude Code

Summary by CodeRabbit

  • Documentation
    • Added guidance on a previously rejected approach, clarifying which event-subscription exemptions are safe to apply.
    • Documented why a broader exemption would be unsound, helping prevent similar issues from being reintroduced.
    • Noted that two existing findings remain tracked as known false positives.

claude added 4 commits June 28, 2026 05:08
A subscription to a process-lived static event (AppDomain.ProcessExit,
Console.CancelKeyPress) from inside a `static class` is not an OWN014 region
escape: a static class has NO instance, so the 'promotes the component instance
to the source's lifetime' premise is vacuously false — its state is already
process-lived by the language definition. Sound by guarantee, not heuristic.

Extends the existing process-lived-subscriber exemption (the WPF `App` singleton,
clsIsApp) with clsIsStatic, gated identically: only the source=="static" region
escape, non-timers. Mined on CsvHelper's static ConsoleHost, whose
ProcessExit/CancelKeyPress shutdown hooks were false OWN014s.

New corpus fixture subscription-static-class-host: before.cs is an INSTANCE host
subscribing this to Console.CancelKeyPress (real OWN014), after.cs is the same as
a static class (silent). case.own models the token form (OWN001) like the sibling
screentogif-systemevents-leak case.

Newtonsoft TraceJsonReader._textWriter stays baselined: it is a JsonTextWriter
over a StringWriter, no-op only because of what it wraps — recognising that needs
wrapper-cascade analysis (unsound to blanket-exempt JsonTextWriter), so the
honest call is to leave it. Re-pointing the oracle at CsvHelper to confirm the
ConsoleHost FPs clear.

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

Live CsvHelper oracle re-run confirms the clsIsStatic exemption clears both
ConsoleHost hooks (AppDomain.ProcessExit / Console.CancelKeyPress) at the source:
Own.NET leak total 2 -> 0, both findings absent from own-only and baselined.
Delete the two CsvHelper baseline entries (no CsvHelper FP entries remain).
Restore 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
Both Codex and CodeRabbit independently flagged the clsIsStatic exemption as
unsound: it skipped EVERY static-source subscription in a static class without a
capture check, silently dropping real captured-local leaks — e.g.
  static class Foo { void Attach(VM vm) {
      SystemEvents.UserPreferenceChanged += (_, _) => vm.Refresh(); } }
where the process-lived event pins the captured vm forever (was OWN014).

A sound narrowing (suppress only when the handler captures nothing) would not
clear the CsvHelper ConsoleHost target either — its shutdown hooks capture
cts/resetEvent locals — so the static-class signal cannot soundly distinguish
that FP from a real captured-local leak. CsvHelper ConsoleHost therefore stays
baselined, same bucket as Newtonsoft serializer.Error.

Reverts the extractor change, the subscription-static-class-host fixture, the doc
update, and restores the two CsvHelper ConsoleHost baseline entries — branch
returns to main. PR #157 is closed (no net change). Caught pre-merge by review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
Document the unsound `|| clsIsStatic` exemption attempted in #157 (reverted
in 488d505) so it is not retried. A static class only rules out an instance
`this`; a lambda handler can still capture a local that a process-lived event
pins for the whole process, so the exemption would silently swallow a real
captured-local leak. A capture-gated narrowing would be sound but still would
not clear CsvHelper ConsoleHost (it captures cts/resetEvent), which therefore
stays baselined.

- Program.cs: ANTI-PATTERN warning comment at the clsIsApp exemption site.
- oracle-known-fps.md: new "Rejected approaches" section with the why.

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: 16a087a3-965e-46b7-b2f9-e89e661a4c7e

📥 Commits

Reviewing files that changed from the base of the PR and between ede20ce and dace9e8.

📒 Files selected for processing (2)
  • docs/notes/oracle-known-fps.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs

📝 Walkthrough

Walkthrough

Adds a multi-line anti-pattern comment in Program.cs forbidding broadening the process-lived App exemption to static classes, and adds a "Rejected approaches" subsection in docs/notes/oracle-known-fps.md documenting the same rationale, the prior trial in PR #157, and the revert in commit 488d505. No functional logic changes.

Static-class exemption rejection notes

Layer / File(s) Summary
Anti-pattern comment and rejection docs
frontend/roslyn/OwnSharp.Extractor/Program.cs, docs/notes/oracle-known-fps.md
Adds a forbidding comment in the process-lived App exemption path and a new "Rejected approaches" subsection documenting the captured-locals unsoundness, the PR #157 trial, and the 488d505 revert.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Possibly related PRs

  • PhysShell/Own.NET#81: Introduced the IsProcessLivedApplication exemption for App singletons that this PR explicitly warns against broadening to static classes.
  • PhysShell/Own.NET#86: Touches the same WPF/process-lived subscriber detection logic and adds fixtures around static-class handler exemptions.
  • PhysShell/Own.NET#28: Modifies subscription source/lambda handling rules in the same region of Program.cs that this PR annotates.

Poem

🐇 A static class can trap a local tight,
And pin it to the process through the night.
We tried the rule, then swiftly turned it back—
A comment now guards the forbidden track.
The baseline holds what we dare not re-add! 🌿

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: documenting that a static-class subscriber exemption was rejected.
Description check ✅ Passed The description follows the template and fills the required sections with relevant details, checks, issue refs, and checklist items.
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.

@PhysShell
PhysShell merged commit 69c79b0 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