Skip to content

feat(analyzers): add REACTOR_WIN2D_001 (UseCanvasResources without .UseSharedDevice)#808

Open
azchohfi wants to merge 4 commits into
azchohfi-spec-060-analyzer-suitefrom
azchohfi-reactor-win2d-001-shared-device
Open

feat(analyzers): add REACTOR_WIN2D_001 (UseCanvasResources without .UseSharedDevice)#808
azchohfi wants to merge 4 commits into
azchohfi-spec-060-analyzer-suitefrom
azchohfi-reactor-win2d-001-shared-device

Conversation

@azchohfi

@azchohfi azchohfi commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

REACTOR_WIN2D_001 — spec 060 §12 (Reactor.Win2D, Error, ✔ fix, niche)

A Win2D canvas that draws UseCanvasResources output but never calls .UseSharedDevice() crashes at draw time. UseCanvasResources builds its resources on Win2D's process-wide shared device (CanvasDevice.GetSharedDevice()); Win2D resources are device-affine, so a default-device canvas drawing them raises a fatal cross-device stowed exception. This is the only Error-severity rule in the suite, so the analyzer is tuned for near-zero false positives.

STEP-1 premise verification (all three APIs confirmed real against source)

The contested 2/5† vote included a REJECT resting on a mistaken "no such API" claim. Verified against source before writing any code:

  • UseCanvasResources hooksrc/Reactor.Advanced/Win2D/Hooks/UseCanvasResources.cs:31 (extension on RenderContext; creates on CanvasDevice.GetSharedDevice(); its XML doc states the cross-device-crash premise verbatim).
  • .UseSharedDevice() modifiersrc/Reactor.Advanced/Win2D/Win2DCanvasModifiers.cs:47 (manual), :54 (animated), :61 (virtual) — exactly the spec-cited line 47.
  • Win2DSharedDeviceGuardsrc/Reactor.Advanced/Win2D/Win2DSharedDeviceGuard.cs (real).

The causal premise holds, so I proceeded without gating.

Detection (low-FP by construction)

Anchored on the canvas factory invocation (Win2DCanvas / Win2DAnimatedCanvas / Win2DVirtualCanvas, confirmed by semantic return type). Fires only when all hold:

  1. The fluent chain provably lacks .UseSharedDevice().
  2. Construction is not opaque — bails on variable/field capture, a raw .Set(...) in the chain, or a with { } mutation (parenthesis-robust).
  3. Causal link: the canvas expression references a local whose initializer is a UseCanvasResources call — i.e. the canvas actually draws a shared-device resource. This is beyond the spec's baseline and eliminates the multi-canvas false positive that matters most for an Error rule.

Fix: appends .UseSharedDevice() to the outermost chain. The bare call always resolves because the rule only fires where UseCanvasResources (an extension in Microsoft.UI.Reactor.Advanced.Win2D) is in scope, which means that namespace — and .UseSharedDevice() from the same namespace — is already imported. No using/qualification insertion needed.

What shipped

  • src/Reactor.Analyzers/Win2DSharedDeviceAnalyzer.cs + Win2DSharedDeviceCodeFix.cs
  • Coupled AnalyzerReleases.Unshipped.md row (RS2000/RS2002 satisfied — clean analyzer build)
  • Row in the template docs/_pipeline/templates/analyzer-architecture.md.dt
  • Per §2.6, the app-author cheat table is intentionally skipped (niche control-author rule; the analyzer DLL's own message carries it)
  • tests/Reactor.Tests/AnalyzerTests/Win2DSharedDeviceAnalyzerTests.cs — 14 tests: positives across all three canvas types, negatives (has modifier; multi-canvas where the bare canvas doesn't draw the resource; no hook; hook but no canvas), near-misses (variable capture, raw .Set, with, parenthesized), and two fix round-trips.

Tests

dotnet test tests/Reactor.Tests -p:Platform=x64 --filter FullyQualifiedName~Win2DSharedDevice → 14/14 pass. Full AnalyzerTests suite → 185/185 (no regressions). Samples sweep: no sample uses UseCanvasResources, and the doc-pipeline app already uses .UseSharedDevice(), so the rule correctly stays silent (§2.4: fires-nowhere is expected for a niche rule whose samples already use the good form — no deliberately-bad sample added).

Base: azchohfi-spec-060-analyzer-suite.

…seSharedDevice)

Adds an Error-severity Roslyn analyzer + code fix for spec 060 §12: a Win2D canvas that draws UseCanvasResources output (created on Win2D's shared device) but never calls .UseSharedDevice() crashes at draw time with a fatal cross-device stowed exception.

The analyzer is deliberately conservative for an Error rule: it fires only on an inline canvas whose fluent chain provably lacks .UseSharedDevice() AND that references a local initialized by a UseCanvasResources hook (the causal draw link). It bails on opaque construction (variable/field capture, raw .Set(...), with { }) and is parenthesis-robust. The code fix appends .UseSharedDevice() to the canvas; the bare call always resolves because the hook's namespace is already imported at any fire site.

Category Reactor.Win2D. Adds the coupled AnalyzerReleases.Unshipped row and the analyzer-architecture template row; per spec 060 §2.6 the app-author cheat table is intentionally skipped for this niche control-author rule. 14 unit tests (positive across all three canvas types, negatives, near-misses, and fix round-trips).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a new Reactor analyzer rule (REACTOR_WIN2D_001) to prevent a Win2D cross-device crash scenario by flagging canvases that draw UseCanvasResources output without opting into Win2D’s shared device, and provides an automatic code fix to append .UseSharedDevice().

Changes:

  • Introduces Win2DSharedDeviceAnalyzer (Error severity) to detect missing shared-device opt-in when UseCanvasResources output is used.
  • Adds Win2DSharedDeviceCodeFix to append .UseSharedDevice() to the outermost fluent canvas expression.
  • Adds unit tests for analyzer + fix, and updates the analyzer release notes + analyzer architecture docs template.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/Reactor.Tests/AnalyzerTests/Win2DSharedDeviceAnalyzerTests.cs Adds unit coverage for the new analyzer and code fix.
src/Reactor.Analyzers/Win2DSharedDeviceCodeFix.cs Implements the code fix that appends .UseSharedDevice() to the fluent chain.
src/Reactor.Analyzers/Win2DSharedDeviceAnalyzer.cs Implements the new REACTOR_WIN2D_001 analyzer logic.
src/Reactor.Analyzers/AnalyzerReleases.Unshipped.md Registers the new rule in unshipped analyzer release notes.
docs/_pipeline/templates/analyzer-architecture.md.dt Adds the new rule to the analyzer architecture documentation template.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Reactor.Analyzers/Win2DSharedDeviceAnalyzer.cs
Comment thread src/Reactor.Analyzers/Win2DSharedDeviceAnalyzer.cs Outdated
Comment thread tests/Reactor.Tests/AnalyzerTests/Win2DSharedDeviceAnalyzerTests.cs
Addresses pr-review + multi-model cross-check findings:

- Require the exact UseCanvasResourcesHook containing type (drop the RenderContext-receiver fallback that could flag unrelated app extensions).

- Make .UseSharedDevice() argument-aware: a literal .UseSharedDevice(false) is a provable opt-out and now fires; dynamic args stay opted-in to avoid false positives.

- Restrict the causal-link scan to the factory's onDraw/onRegionDraw callback (not onUpdate, redrawKey, or event-handler modifiers) so the rule fires only when the resource is actually drawn.

- Code fix now adds 'using Microsoft.UI.Reactor.Advanced.Win2D;' when it is not in scope at the fix site (scope-aware; handles a fully-qualified static hook call), guaranteeing the appended call binds.

- Drop the dead IFieldSymbol branch. Add coverage: explicit-false fires, true/dynamic opt-in, onUpdate-only, non-callback arg, assignment RHS, unrelated same-named factory, and the using-insertion round-trip. 22 tests total.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread src/Reactor.Analyzers/Win2DSharedDeviceCodeFix.cs
Comment thread tests/Reactor.Tests/AnalyzerTests/Win2DSharedDeviceAnalyzerTests.cs
Copilot review: the code fix forced CRLF via SyntaxFactory.CarriageReturnLineFeed, but the repo's .editorconfig sets end_of_line = lf. The fix now reuses the document's existing newline (defaulting to LF) so it never injects a mixed-EOL diff, and the using-insertion test derives the newline from the source string so it passes under both LF (CI) and CRLF (local) checkouts.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread src/Reactor.Analyzers/Win2DSharedDeviceAnalyzer.cs Outdated
Comment thread src/Reactor.Analyzers/Win2DSharedDeviceCodeFix.cs Outdated
…atch

Copilot review: (1) rephrase the diagnostic Description so the docs path is not immediately followed by a sentence period (avoids a linkifier swallowing the '.'); (2) IsWin2DUsing now strips an optional global:: prefix so 'using global::Microsoft.UI.Reactor.Advanced.Win2D;' is recognized as in scope and the fix does not insert a duplicate using (CS0105). Adds a regression test.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

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