Repository navigation
Conversation
…diagnostics
Adds UnsafeContextCodeFixProvider, which fixes CS9360, CS9361, CS9362, CS9363
and CS9376 under the updated memory-safety rules by introducing audited inner
contexts: `unsafe { }` blocks (preceded by `// SAFETY: To be audited`) or
`/* SAFETY: To be audited */ unsafe(...)` expressions. Every candidate is
validated by rebinding the member; members without a valid candidate are left
unchanged.
Replaces the incomplete RequiresUnsafeCodeFixProvider.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cfef4956-ea1a-4a4a-926d-9ae912bb0e2f
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
|
Tagging subscribers to this area: @agocke, @dotnet/illink |
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Constructor-initializer capture, cross-configuration inference, trivia preservation, and candidate-exhaustion issues remain unresolved.
4 open findings
What changed in this PR
Adds a non-shipping ILLink code fixer that introduces narrowly scoped, audited unsafe contexts for unsafe-v2 compiler diagnostics.
Changes:
- Replaces the legacy unsafe-modifier fixer with block/expression planning.
- Validates edits through recompilation and supports multi-pass merging.
- Adds coverage for statements, declarations, CoreLib patterns, and code actions.
| File | Description |
|---|---|
UnsafeContextCodeFixTests.Statements.cs |
Tests statement transformations. |
UnsafeContextCodeFixTests.MultiPass.cs |
Tests repeated/configuration passes. |
UnsafeContextCodeFixTests.Declarations.cs |
Tests declaration splitting. |
UnsafeContextCodeFixTests.cs |
Provides test infrastructure and core cases. |
UnsafeContextCodeFixTests.CoreLibPatterns.cs |
Tests representative CoreLib patterns. |
UnsafeContextCodeFixTests.Actions.cs |
Tests action policies and unsupported cases. |
UnsafeTarget.cs |
Maps diagnostics to operations and scopes. |
UnsafeContextValidator.cs |
Recompiles candidates and compares diagnostics. |
UnsafeContextRegion.cs |
Models candidate regions and refinements. |
UnsafeContextPlanner.cs |
Plans block and expression contexts. |
UnsafeContextFormatting.cs |
Preserves layout and indentation. |
UnsafeContextFacts.cs |
Defines diagnostics, markers, and feature detection. |
UnsafeContextEdit.cs |
Implements expression and body edits. |
UnsafeContextDocumentFixer.cs |
Coordinates planning and validation. |
UnsafeContextCodeFixProvider.cs |
Registers actions and Fix All. |
SyntaxLayoutExtensions.cs |
Adds syntax-layout helpers. |
StatementShape.cs |
Models compound-statement structure. |
StatementList.cs |
Models statement scopes and target spans. |
StatementBlockEdit.cs |
Generates unsafe statement blocks. |
PlanningContext.cs |
Holds semantic planning state. |
PlaceholderContexts.cs |
Recognizes and merges placeholder contexts. |
ExpressionWrapPlanner.cs |
Plans unsafe(...) wrappers. |
DirectiveFacts.cs |
Handles conditional-compilation boundaries. |
DeclarationSplitter.cs |
Hoists declarations safely around blocks. |
BlockEditFactory.cs |
Builds and closes block ranges. |
Resources.resx |
Adds localized action titles. |
RequiresUnsafeCodeFixProvider.cs |
Removes the legacy fixer. |
ILLink.CodeFixProvider.csproj |
Configures non-Release fixer compilation. |
🧠 Review effort: Balanced
- Don't split `var` locals whose initializer contains preprocessor directives; the inferred type may differ in another configuration. Deconstruction declarations now pass their source to the same check. - Keep inline comments of split declarations (e.g. `/* note */ int a = ...;`). - Remove the validation round cap: it could drop a region before its later candidates were tried. Convergence is guaranteed because each failure advances a region to a later candidate. - Add a regression test for CallerArgumentExpression-captured arguments in `: base(...)` / `: this(...)` initializers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: cfef4956-ea1a-4a4a-926d-9ae912bb0e2f
20 of 27 tasks
…ests - Keep the trailing trivia of the declared identifier in the assignment, e.g. `value /* identifier */ = /* assignment */ Get();`. - Keep comments after the type in hoisted declarations, e.g. `int /* explanation */ value;` for `Get(out int /* explanation */ value)`. - Test that blocks containing `await` are introduced (allowed under the updated rules) and that a method-group conversion falls back to a getter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: cfef4956-ea1a-4a4a-926d-9ae912bb0e2f
This was referenced Oct 7, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Adds
UnsafeContextCodeFixProvidertoILLink.CodeFixProvider. Under the updated memory-safety rules (unsafe evolution), it fixes these Roslyn errors:It fixes each one by introducing an inner unsafe context around the operation. That context is either an
unsafe { }block or anunsafe(...)expression, and each one carries a placeholder audit marker. The fixer never changes modifiers, signatures or project options - this is a job for fixers that run before it, see #131451It replaces the incomplete
RequiresUnsafeCodeFixProvider, which is deleted along with its tests.Output
Code actions
unsafe(...)(default; used by Fix All anddotnet format)unsafe(...)first, falling back to blocksHow a context is chosen
if, loops,switch,try, ...) are fixed inside that body.unsafe(...)in that header.varlocal is split only when its type can be written in source and doesn't depend on#ifor ausingalias.stackallocbecomescoped.outvariables and deconstructions are lifted.usingdeclarations extend the block to the end of the enclosing scope.Safety
yield,CallerArgumentExpression-captured arguments, or an#ifgroup that is cut in half.#ifarms are scanned. Their text is scanned so that a block does not hide a declaration from, or move a declaration away from, code in another configuration.Multiple passes and build configurations
The fixer can be run once per configuration (for example x64 Checked and then arm64 Release).
SAFETY:text are treated as audited and are left untouched.Known limitations
In System.Private.CoreLib, 8 diagnostics are left unfixed. They fall into three patterns, and each needs a manual change.
1. Calls to unsafe constructors through
: base(...)/: this(...)(3 occurrences)A constructor initializer is neither a statement nor an expression, so no unsafe context can enclose the call. The only fix is to change the safety of one of the constructors, and the fixer never changes modifiers. This is a job for a
SynchronizeUnsafeContractCodeFixProviderso by the time we run this analyzer we shouldn't see such patterns.2. An unsafe call that takes a lambda argument (1 occurrence)
Both a block and
unsafe(...)would enclose the lambda, which would make its body unsafe without an audit. Not a big deal, I guess. Manual fix: move the lambda into a local first, then wrap the call.3. A
voidexpression body that contains#if(4 occurrences)voidmethod must be a statement expression, sounsafe(...)cannot wrap the whole call.Castcalls separately would put two wrappers in one statement, and a statement gets at most one.#ifarm is not parsed and would not carry over.The
ulongoverload has the same shape without#if, and it is fixed by converting it to a block body. Manual fix: convert these two members to block bodies with the#ifinside.Running it on CoreLib
The root
Directory.Build.propssetsFeatureswithout appending to it, so the v2 feature flag has to be injected throughCustomAfterMicrosoftCommonTargets.