Skip to content

audit(xaml): Phase-2 join — XAML203 view-subscription leak (build-free)#117

Merged
PhysShell merged 3 commits into
mainfrom
claude/oun-net-html-analyzer-drff2t
Jun 26, 2026
Merged

audit(xaml): Phase-2 join — XAML203 view-subscription leak (build-free)#117
PhysShell merged 3 commits into
mainfrom
claude/oun-net-html-analyzer-drff2t

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 26, 2026

Copy link
Copy Markdown
Owner

First slice of the XAML ↔ C# Phase-2 join from the design note — the step where the interprocedural engine stops being decoration and starts accusing XAML with evidence. Follows #114 (Phase-1 markup rules) and #115 (the facts layer).

The one design decision: link by convention, not by .g.cs

WPF's markup compiler emits a .g.cs (InitializeComponent / IComponentConnector glue, x:Name fields, event += handler wiring) — but only into obj/ after a successful markup-compile, which would drag the join into the build-required tier, and a freshly-cloned legacy target (the kind own-check exists for) has no current .g.cs. We don't need it: the wiring it encodes is a fixed contract we synthesize without building —

  • x:Class="App.Views.CustomerView" → code-behind type CustomerView (OwnIR components[].name)
  • Loaded="OnLoaded" / Click="OnSave" → method on that type
  • x:Name="btn" → generated field
  • {Binding Qty} → DataContext property

So the join stays build-free (Linux CI, non-compiling solutions). .g.cs is reserved as an optional build-tier ground-truth cross-check (catch compiler-only edge cases like attached events / EventSetter connection-ids), folded in later like the other build-required tiers — never the mechanism. BAML (post-build binary) is off-roadmap.

What's in it

  • audit/static/tools/xaml_join.pyjoin(xaml-facts, OwnIR) → XAML203. A view whose x:Class component has an OwnIR subscription flagged released=false, wired from a load-lifecycle handler (Loaded/Initialized/DataContextChanged), is the closed-view-retained leak — anchored at the XAML site, naming the C# subscription. released=false is authoritative (the acquire/release engine already checked for a matching -= across the whole class, code-behind included), so no XAML Unloaded heuristic. SARIF emit + CLI + 8-check selftest.
  • xaml_facts.py — also capture x:Name (named_elements): the element→field link the join uses.
  • own-check.sh — new --emit-facts <path> persists the OwnIR facts the extractor already produces; owncheck.py writes own-check.facts.json so the join has a real input in an SDK run. The verdict is unchanged.
  • run_static.py — runs the join whenever xaml-facts.json + own-check.facts.json are both present and folds xaml-join.sarif into the pipeline (opportunistic, like the other pickups). + integration selftest.
  • taxonomyXAML203 → category 2 (subscription leak, P1). Note: the leak lives in the .xaml.cs, a different basename, so XAML203 does not spatially cluster with the own-check OWN001 — the join is the cross-source link, carried in the message.
  • design note + README — the convention-as-link decision, the first rule, and .g.cs/BAML as deferred build-tier ground-truth.

Example output

For a view CustomerView.xaml (x:Class, Loaded="OnLoaded") whose code-behind subscribes _bus.CustomerChanged without -=:

Views/CustomerView.xaml:1 [P1 · xaml-subscription-leak] — view CustomerView wires Loaded=OnLoaded and subscribes without releasing [_bus.CustomerChanged (Views/CustomerView.xaml.cs:21)]; a closed view is retained — add the matching unsubscribe

What's deferred (next increments)

  • Binding-path-hotness (XAML200/204) — needs the DataContext type, rarely static in markup.
  • .g.cs ground-truth cross-check — build-tier validation of the convention-derived wiring.
  • Phase 3 runtime correlation — promote XAML203 to a measured retention path via the heap walker.

Validation

Full audit selftest suite green (normalize/score/report/xaml_check/xaml_facts/xaml_join/run_static/ingest = 173 checks), ruff check . clean, bash -n own-check.sh clean. The join and its wiring gate on Linux CI with no .NET.

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added a new XAML Phase-2 join analysis that correlates XAML markup with code facts to detect XAML203 ViewSubscribesWithoutRelease.
    • Extended XAML documentation output to include captured named XAML elements for richer insight.
  • Bug Fixes
    • Improved reporting so new XAML join findings are merged into the existing SARIF output.
    • Added safeguards to ensure the join runs only when the required, up-to-date facts are produced for the current run.
  • Tests
    • Expanded CI/self-tests with coverage for the new join tier and freshness behavior.
  • Documentation
    • Updated audit README and design notes to describe the new XAML analysis flow and status slice.

First slice of the XAML<->C# Phase-2 join from the design note. Links the two
fact sources by the deterministic XAML naming convention — NOT the .g.cs build
artifact — so it stays build-free (Linux CI, non-compiling solutions).

- xaml_join.py: join(xaml-facts, OwnIR) -> XAML203. A view whose x:Class
  component has an OwnIR subscription flagged released=false, wired from a
  load-lifecycle handler (Loaded/Initialized/DataContextChanged), is the
  closed-view-retained leak; anchored at the XAML site, names the C# subscription.
  released=false is authoritative (the engine already checked for -= across the
  class), so no XAML Unloaded heuristic. SARIF emit + CLI + 8-check selftest.
- xaml_facts.py: also capture x:Name (named_elements) — the join's element->field link.
- own-check.sh: add --emit-facts to persist the OwnIR facts; owncheck.py writes
  own-check.facts.json so the join has a real input in an SDK run.
- run_static.py: run the join when xaml-facts.json + own-check.facts.json are both
  present; fold xaml-join.sarif into the pipeline (+ integration selftest).
- taxonomy: XAML203 -> category 2 (subscription leak, P1).
- design note + README: document the convention-as-link decision, the first rule,
  and .g.cs/BAML as deferred build-tier ground-truth (not the mechanism).

Binding-path-hotness (XAML200/204, needs the DataContext type) is the next
increment, deliberately not guessed here.

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

coderabbitai Bot commented Jun 26, 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: 0dcedb26-bad6-47ae-bf5f-0f1c98f1904e

📥 Commits

Reviewing files that changed from the base of the PR and between a940326 and d41fc65.

📒 Files selected for processing (3)
  • audit/static/run_static.py
  • audit/static/tools/owncheck.py
  • audit/static/tools/xaml_join.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • audit/static/tools/owncheck.py
  • audit/static/tools/xaml_join.py

📝 Walkthrough

Walkthrough

The audit pipeline now emits OwnIR facts, extends XAML fact collection with named elements, adds a XAML join that reports XAML203, wires the join into static aggregation when both fact files are present, and updates CI, docs, and taxonomy.

Changes

XAML Phase-2 join

Layer / File(s) Summary
OwnIR facts emission
scripts/own-check.sh, audit/static/tools/owncheck.py
own-check.sh accepts --emit-facts, copies the temporary OwnIR facts to the requested path, and run_own_check returns that artifact in status.
XAML named elements
audit/static/tools/xaml_facts.py
XAML fact generation now records Name/x:Name property elements as named_elements, totals them, and updates the selftest fixture and assertion.
XAML203 join tool
audit/static/tools/xaml_join.py
The new CLI resolves XAML x:Class documents to unreleased OwnIR event subscriptions, emits SARIF, and includes matching and suppression cases in selftest.
Static integration
audit/static/run_static.py, audit/static/taxonomy/categories.yml
run_static.py imports and runs the join when both fact files exist, adds its SARIF to aggregation, updates selftest expectations, and categories.yml maps XAML203 to xaml-subscription-leak.
CI and docs
.github/workflows/ci.yml, audit/README.md, docs/notes/xaml-analyzer-design.md
The audit selftest workflow, README, and design note reference the new join selftest and the Phase-2 join contract.

Sequence Diagram(s)

sequenceDiagram
  participant XamlFacts as xaml_facts.py
  participant OwnCheckPy as owncheck.py
  participant OwnCheckSh as own-check.sh
  participant RunStatic as run_static.py
  participant XamlJoin as xaml_join.py
  participant Aggregate as aggregate()

  XamlFacts-->>RunStatic: xaml-facts.json
  OwnCheckPy->>OwnCheckSh: --emit-facts <facts_path>
  OwnCheckSh-->>OwnCheckPy: own-check.facts.json
  RunStatic->>XamlJoin: run_join(xaml-facts.json, own-check.facts.json)
  XamlJoin-->>RunStatic: xaml-join.sarif and tier status
  RunStatic->>Aggregate: include xaml-join SARIF
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PhysShell/Own.NET#10: This PR also changes scripts/own-check.sh; the current PR adds --emit-facts <path> so the same facts pipeline can feed the new XAML join.
  • PhysShell/Own.NET#100: Both PRs extend the static audit pipeline around XAML processing and SARIF aggregation, including the run_static.py integration path.
  • PhysShell/Own.NET#115: Both PRs touch the XAML facts seam; the current PR extends xaml_facts.py and consumes xaml-facts.json in the new join step.

Poem

Hop, hop, I join the clues at night,
With facts and facts in tidy flight.
🐰 XAML sparks and OwnIR hum,
A little leak gets found by plum.
I twitch my nose—new paths align,
And audit burrows feel just fine!

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: a build-free Phase-2 XAML join for XAML203 view-subscription leaks.
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/oun-net-html-analyzer-drff2t

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: 7bb7912e4e

ℹ️ 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 thread audit/static/tools/xaml_join.py Outdated
Comment thread audit/static/tools/xaml_join.py Outdated
…-name views (Codex)

Two correctness fixes from the Codex review on PR #117:

- XAML203 now fires only on event-subscription records. OwnIR's
  components[].subscriptions is an umbrella list that also carries timer /
  IDisposable / pool leaks tagged via `resource`; _unreleased previously treated
  all released=false entries as event subscriptions, mislabeling e.g. an
  unreleased DispatcherTimer as a cat-2 [resource: subscription token] leak.
  Restricted to subscription-family resources (untagged += or subscribe/
  subscription token); timer/disposable/pool stay own-check's cat-3/1.
- Same-simple-name views are disambiguated by code-behind path. The component
  index keys on the unqualified type name, so Admin.CustomerView and
  Sales.CustomerView collided and a leak in one could land XAML203 on the other's
  XAML. _linked_components now requires code-behind-path correspondence
  (segment-aware suffix match of the .xaml stem vs the .xaml.cs/.cs stem) when the
  simple name is ambiguous, and links nothing rather than cross-link.

Selftest grows to 11 checks (timer-not-XAML203; Sales leak lands on Sales XAML,
never Admin's).

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

Copy link
Copy Markdown
Owner Author

The two review findings (filter XAML203 to event subscriptions; disambiguate same-simple-name views by code-behind path) are fixed in a940326, and all CI is green on that commit. The earlier reviews ran against 7bb7912 / hit a rate limit, so the fix commit hasn't been reviewed yet — requesting a fresh pass:

@coderabbitai review
@codex review


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

@PhysShell Understood — I’ll do a fresh pass on the updated PR state, including the fixes in a940326.

✅ Action performed

Full review finished.

@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: a940326101

ℹ️ 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 thread audit/static/run_static.py Outdated
Comment thread audit/static/tools/xaml_join.py Outdated

@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: 2

🤖 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 `@audit/static/tools/owncheck.py`:
- Line 42: Clear stale own-check output in the static tools flow: in
owncheck.py, ensure own-check.facts.json is removed before invoking own-check
and also deleted on any non-success path so run_static only sees facts from the
current successful run. Update the logic around facts_path, own-check
invocation, and the failure handling branches to prevent xaml-join from
consuming leftover data from a previous target or commit.

In `@audit/static/tools/xaml_join.py`:
- Around line 120-123: The simple-name lookup in _component_index() can still
return the wrong file when there is only one basename match, so update the
matching logic to path-check that single candidate through _path_corresponds()
as well. In the branch that handles cands from by_name.get(...) in the XAML join
flow, only return the lone match when its file metadata is missing; otherwise
filter it against doc["file"] the same way as the multi-candidate case to avoid
cross-linking the wrong XAML file.
🪄 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: a0c0fa2a-3879-48b4-923f-98210e5e933f

📥 Commits

Reviewing files that changed from the base of the PR and between 61b278f and a940326.

📒 Files selected for processing (9)
  • .github/workflows/ci.yml
  • audit/README.md
  • audit/static/run_static.py
  • audit/static/taxonomy/categories.yml
  • audit/static/tools/owncheck.py
  • audit/static/tools/xaml_facts.py
  • audit/static/tools/xaml_join.py
  • docs/notes/xaml-analyzer-design.md
  • scripts/own-check.sh

Comment thread audit/static/tools/owncheck.py
Comment thread audit/static/tools/xaml_join.py Outdated
…eck, stale cleanup

Round-two review fixes (Codex + CodeRabbit) on the Phase-2 join:

- xaml_join: include `resource: "capture"` in the XAML203 filter. capture is a
  static/process-lived event subscription that retains the subscriber (the OWN014
  region-escape case) — exactly the closed-view-retained leak XAML203 targets —
  so the prior allow-list wrongly excluded it. (Codex)
- xaml_join: path-check the unique simple-name match too. The name index drops the
  namespace, so a lone basename hit could cross-link App.Views.CustomerView.xaml to
  an unrelated Legacy.CustomerView.cs. _linked_components now path-checks every
  candidate and only falls back to a lone match with no file metadata. (CodeRabbit)
- run_static: gate the join on the fact files THIS run produced (the statuses'
  `facts` paths), not bare existence — a re-run into an existing --out, or a profile
  without xaml/own-check (or no SDK), must not join a previous target's stale facts.
  + selftest: join runs only when both producers ran this invocation; a stale
  own-check.facts.json on disk is NOT joined. (Codex)
- owncheck: clear own-check.facts.json up front so a failed/unavailable run leaves
  no stale facts behind (own-check.sh writes it only on success). (CodeRabbit)

Selftests: xaml_join 13, run_static 22.

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