Skip to content

feat(p1a): curate XmlReader/XmlWriter/JsonDocument as owned factories#153

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

feat(p1a): curate XmlReader/XmlWriter/JsonDocument as owned factories#153
PhysShell merged 3 commits into
mainfrom
claude/mos-ownership-summary-n3q3j4

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Что и зачем

P1a stdlib contract pack (producer-сторона): расширяю распознавание owned-возвращающих фабрик тремя высокоценными, низко-FP семействами BCL, чьи результаты вызывающий обязан диспоузить — XmlReader.Create, XmlWriter.Create (System.Xml) и JsonDocument.Parse (System.Text.Json, который пулит память и часто течёт при потере ссылки). Тот же producer-контракт, что у File.Open*/crypto Create*: var doc = JsonDocument.Parse(json) с потерей doc теперь даёт OWN001 на месте вызова фабрики, а use/double-dispose результата — OWN002/OWN003.

Обе синхронизированные половины обновлены в лок-степе: экстрактор IsOwningFactory (Program.cs) решает, эмитить ли факт вызова фабрики (по разрешённому символу: static + результат реализует IDisposable + тип/namespace, так что Task-возвращающий JsonDocument.ParseAsync исключён); мост _BCL_FRESH_BY_NS (ownir.py) распознаёт имя callee как fresh.

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

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

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

  • python tests/run_tests.py — зелёный (ownir bridge 206/206, +5 новых P1a-проверок; ownership 44/44, остальные сюиты)
  • ruff check . и mypy (--strict, 17 файлов) — чисто
  • селфтесты затронутых скриптов — н/п

ВАЖНО: в окружении разработки нет dotnet, поэтому C#-половина (экстрактор) не собиралась локально — она проверяется CI: golden C# compiles & runs, C# leak extractor (Roslyn) → OwnIR → core, и real-C# corpus benchmark (recall + specificity). Bridge-половина покрыта новыми тестами полностью (leak → OWN001 для каждой фабрики, bare и namespace-qualified; диспоуз результата — чисто).

Связанные issue

Нет issue для закрытия. Refs: дизайн-нота P-005 / P-006 (P1a — «recall ladder over a fixed precision floor», producer-половина контрактов).

Чеклист

  • изменение покрыто тестом (tests/test_ownir.py: +5 P1a-кейсов на bridge-стороне)
  • README/docs обновлены — не требуется (контракт описан в комментариях таблицы/фабрики)
  • коммиты в conventional-commit стиле (feat(p1a): …)

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved leak detection for “fresh” owned disposable results from additional standard library factory methods.
    • Calls to XmlReader.Create, XmlWriter.Create, and JsonDocument.Parse are now more reliably flagged when results aren’t disposed, while proper release disposal is treated as clean.
  • Tests
    • Expanded coverage to include these additional factories (including namespace-qualified identities) and added assertions confirming correct cleanup behavior after release.

P1a stdlib contract pack (producer side): extend the curated owned-returning
factory recognition with three high-value, low-FP BCL families whose result the
caller must dispose — `XmlReader.Create`, `XmlWriter.Create` (System.Xml) and
`JsonDocument.Parse` (System.Text.Json, which pools memory and is a common real
leak when dropped). Same producer-half contract as File.Open* / crypto Create*:
a `var doc = JsonDocument.Parse(json)` that drops `doc` now surfaces OWN001 at
the factory call, and use/double-dispose of the result is OWN002/OWN003.

Both synced halves are updated in lockstep:
  - extractor `IsOwningFactory` (Program.cs) — gates whether to EMIT the factory
    call fact, by resolved symbol (static + result implements IDisposable + the
    type/namespace), so a Task-returning `JsonDocument.ParseAsync` is excluded;
  - bridge `_BCL_FRESH_BY_NS` (ownir.py) — recognises the callee name as `fresh`.

Bridge half is covered by new tests (leak → OWN001 for each, bare and
namespace-qualified; a disposed result stays clean): ownir 206/206, ownership
44/44, mypy --strict, ruff all green locally.

NOTE: this environment has no dotnet, so the extractor (C#) half is not built
locally — it is verified by CI (golden C# compiles & runs, C# leak extractor,
and the real-C# corpus benchmark for recall/specificity).

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

coderabbitai Bot commented Jun 27, 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: 36db8a8e-dbbb-4afe-be95-455f01104c53

📥 Commits

Reviewing files that changed from the base of the PR and between bac905a and 83edff6.

📒 Files selected for processing (1)
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/roslyn/OwnSharp.Extractor/Program.cs

📝 Walkthrough

Walkthrough

Factory detection now recognizes XmlReader.Create, XmlWriter.Create, and JsonDocument.Parse as owned disposable factories. The OwnIR table and tests were updated to match the same factory set.

Changes

BCL factory recognition update

Layer / File(s) Summary
Extractor factory detection
frontend/roslyn/OwnSharp.Extractor/Program.cs
IsOwningFactory now recognizes XmlReader.Create, XmlWriter.Create, and JsonDocument.Parse when the return type implements IDisposable, and rejects overloads with disposable arguments.
OwnIR table update
ownlang/ownir.py
The _BCL_FRESH_BY_NS table adds the same factory methods for fresh owned return inference.
Factory leak coverage
tests/test_ownir.py
Tests assert leaked factory results produce OWN001, and disposing an XmlReader.Create result remains clean.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PhysShell/Own.NET#60 — Updates IsOwningFactory in frontend/roslyn/OwnSharp.Extractor/Program.cs to classify additional static BCL factory methods as owned disposables.
  • PhysShell/Own.NET#127 — Extends the curated Tier-B BCL factory table in ownlang/ownir.py and adds matching factory-leak tests.
  • PhysShell/Own.NET#136 — Modifies the _BCL_FRESH_BY_NS factory lookup used for fresh owned disposable inference.

Poem

A rabbit peeks in XML streams,
Then naps by Json’s silver beams.
Three factories now hop into view,
And leaks get caught the bunny way too. 🐇

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly summarizes the main change: adding owned-factory recognition for XmlReader, XmlWriter, and JsonDocument.
Description check ✅ Passed The description follows the required template and fills all sections with relevant details, checks, and validation notes.
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.

@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.

🧹 Nitpick comments (1)
tests/test_ownir.py (1)

1697-1709: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add explicit fully-qualified System.Xml regressions.

Lines 1697-1709 only cover the qualified form for JsonDocument.Parse. Since this PR also adds System.Xml.XmlReader.Create and System.Xml.XmlWriter.Create, please include those two FQNs here as well so the new entries are pinned end-to-end.

Suggested diff
-    for fresh_callee, ln in (("XmlReader.Create", 5), ("XmlWriter.Create", 6),
-                             ("JsonDocument.Parse", 7),
-                             ("System.Text.Json.JsonDocument.Parse", 8)):
+    for fresh_callee, ln in (("XmlReader.Create", 5),
+                             ("System.Xml.XmlReader.Create", 6),
+                             ("XmlWriter.Create", 7),
+                             ("System.Xml.XmlWriter.Create", 8),
+                             ("JsonDocument.Parse", 9),
+                             ("System.Text.Json.JsonDocument.Parse", 10)):
🤖 Prompt for 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.

In `@tests/test_ownir.py` around lines 1697 - 1709, Extend the regression coverage
in the existing P1a acquire/leak test loop by adding explicit fully-qualified
System.Xml cases for XmlReader.Create and XmlWriter.Create, alongside the
current JsonDocument.Parse FQNs. Update the fresh_callee list in the test block
around _bcl so both System.Xml.XmlReader.Create and System.Xml.XmlWriter.Create
are asserted to leak as OWN001 when unreleased, matching the pattern already
used for System.Text.Json.JsonDocument.Parse.
🤖 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.

Nitpick comments:
In `@tests/test_ownir.py`:
- Around line 1697-1709: Extend the regression coverage in the existing P1a
acquire/leak test loop by adding explicit fully-qualified System.Xml cases for
XmlReader.Create and XmlWriter.Create, alongside the current JsonDocument.Parse
FQNs. Update the fresh_callee list in the test block around _bcl so both
System.Xml.XmlReader.Create and System.Xml.XmlWriter.Create are asserted to leak
as OWN001 when unreleased, matching the pattern already used for
System.Text.Json.JsonDocument.Parse.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5eaed82d-49f6-4385-9bbe-10694d99a301

📥 Commits

Reviewing files that changed from the base of the PR and between 8b878de and 77bfc07.

📒 Files selected for processing (3)
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • ownlang/ownir.py
  • tests/test_ownir.py

CodeRabbit: the P1a leak loop only exercised the FQN form for
JsonDocument.Parse. Add System.Xml.XmlReader.Create and
System.Xml.XmlWriter.Create FQNs too, so each new table entry is pinned
end-to-end (bare + namespace-qualified). ownir 208/208, ruff clean.

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

@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: 77bfc07d69

ℹ️ 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 +2288 to +2290
&& ((sym.Name == "Create"
&& sym.ContainingType is { Name: "XmlReader" or "XmlWriter" } xt
&& IsInNamespace(xt, "System", "Xml"))

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 Preserve ownership of XmlReader input streams

Please don't classify all XmlReader.Create/XmlWriter.Create overloads as pure factories without also handling their disposable inputs. The extractor's escape pass removes any candidate disposable passed as an argument, and this factory branch later emits only an acquire for the reader/writer; in var fs = File.OpenRead(p); var xr = XmlReader.Create(fs); xr.Dispose();, fs is dropped from tracking even though XmlReaderSettings.CloseInput defaults to false, so the caller-owned stream leak is suppressed. Either restrict the matched overloads or model these inputs as borrowed/non-escaping.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Good catch — fixed in 83edff6. You're right that the Stream/TextReader/TextWriter overloads don't own their input (CloseInput/CloseOutput default false, and JsonDocument.Parse never closes its stream), so classifying them as pure factories let the escape pass drop the caller-owned input and suppress its leak.

Went with precision over recall (the project's stated floor): the new factory branch now declines the claim whenever any argument resolves to an IDisposable (!AnyDisposableArgument). So XmlReader.Create(fs) / JsonDocument.Parse(stream) are no longer treated as factories — fs stays tracked and its leak is reported — while the common no-disposable-arg overloads (XmlReader.Create("file.xml"), JsonDocument.Parse(jsonString)) still resolve and catch a dropped reader/document. Modelling the input as borrowed-non-escaping (your option b) would keep recall on those overloads too, but it's a deeper escape-pass change; I took the conservative narrowing for now. No dotnet locally, so CI (golden C# / leak extractor / corpus benchmark) verifies the build + specificity.


Generated by Claude Code

…pure factories

XmlReader.Create / XmlWriter.Create / JsonDocument.Parse have overloads that take
a caller-owned disposable input (a Stream / TextReader / TextWriter) which they do
NOT dispose by default (XmlReaderSettings.CloseInput and XmlWriterSettings.CloseOutput
default false; JsonDocument.Parse never closes its stream). Classifying those as
pure owned factories let the extractor's escape pass drop the input argument from
leak tracking, suppressing a real leak of the caller-owned stream — e.g.
`var fs = File.OpenRead(p); var xr = XmlReader.Create(fs); xr.Dispose();` leaked
`fs` invisibly. (Codex P2.)

Gate the new factory branch on `!AnyDisposableArgument`: decline the factory claim
whenever any argument resolves to an IDisposable. The common string / URI / path
overloads have no disposable arg and still resolve (recall preserved there);
precision over recall on the wrapping overloads — never suppress an input leak.

C#-only (no dotnet locally): verified by CI (golden C#, C# leak extractor, corpus
benchmark). Bridge table + tests unchanged — the table is consulted only when the
extractor emits the factory fact, which now excludes the disposable-input overloads.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
@PhysShell
PhysShell merged commit e7a8fec into main Jun 27, 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