Skip to content

Thread-safe task analyzer (without IL decompiler)#13444

Merged
JanProvaznik merged 7 commits intodotnet:mainfrom
JanProvaznik:threadsafe-analyzer-v2
Mar 30, 2026
Merged

Thread-safe task analyzer (without IL decompiler)#13444
JanProvaznik merged 7 commits intodotnet:mainfrom
JanProvaznik:threadsafe-analyzer-v2

Conversation

@JanProvaznik
Copy link
Copy Markdown
Member

Thread-Safe Task Analyzer

Adds a Roslyn analyzer that detects unsafe API usage in MSBuild task implementations, guiding task authors toward thread-safe patterns required for multithreaded task execution (IMultiThreadableTask).

Diagnostic Rules

ID Default Severity Title
MSBuildTask0001 Error API is never safe in MSBuild tasks (Console.*, Environment.Exit, ThreadPool, etc.)
MSBuildTask0002 Warning / Info ¹ API requires TaskEnvironment alternative
MSBuildTask0003 Warning / Info ¹ File system API requires absolute path
MSBuildTask0004 Warning / Info ¹ API may cause issues (Assembly.Load, Activator, etc.)
MSBuildTask0005 Warning / Info ¹ Transitive unsafe API in call chain (source-level BFS)

¹ Warning for IMultiThreadableTask / [MSBuildMultiThreadableTask], Info for plain ITask. Users can configure via .editorconfig.

What's included

  • Core analyzer (MultiThreadableTaskAnalyzer): Per-type scoped analysis via RegisterSymbolStartAction
  • Transitive analyzer (TransitiveCallChainAnalyzer): Source-level BFS call graph traces unsafe APIs through helper methods
  • Code fix provider: Auto-fixes for MSBuildTask0002 (TaskEnvironment replacements) and MSBuildTask0003 (GetAbsolutePath wrapping)
  • Shared helpers (SharedAnalyzerHelpers): Deduplicated path safety analysis and banned API resolution
  • 109 unit tests passing

Build integration

  • Added to MSBuild.slnx
  • /p:BuildAnalyzer=true enables analyzer on Tasks.csproj
  • Produces 56 warnings on Tasks project (34 env + 12 path + 10 transitive), 0 false positives

Safe pattern recognition (zero false positives verified)

  • ITaskItem.GetMetadata("FullPath")
  • Path.Combine(absolutePath, ...)
  • Path.GetDirectoryName(absolutePath)
  • AbsolutePath type (implicit conversion)
  • File.WriteAllText(path, contents) — only path param flagged

What's NOT included (preserved in threadsafe-analyzer branch)

  • IL decompiler (ILCallGraphExtender.cs) — 1,011 lines of raw IL byte parsing with 6 verified opcode bugs
  • Demo project (~1,600 lines)
  • Cross-assembly IL tests (~870 lines)

Testing

  • Stress-tested on MSBuild Tasks project, external task libraries, and SDK-style task patterns
  • Zero false positives across all test scenarios

Adds a Roslyn analyzer for MSBuild task thread safety with:

Core analyzer (MultiThreadableTaskAnalyzer):
- MSBuildTask0001: Critical APIs banned in all tasks (Console.*, Environment.Exit)
- MSBuildTask0002: APIs requiring TaskEnvironment alternatives
- MSBuildTask0003: File APIs requiring absolute paths
- MSBuildTask0004: Potentially problematic APIs (Assembly.Load, etc.)

Transitive analysis (TransitiveCallChainAnalyzer):
- MSBuildTask0005: Source-level BFS call graph traces unsafe APIs through helpers

Dual severity: Warning for IMultiThreadableTask, Info for plain ITask.

Code fix provider for MSBuildTask0002/0003 with TaskEnvironment replacements.

Build integration:
- Added to MSBuild.slnx
- Tasks.csproj: /p:BuildAnalyzer=true enables analyzer
- 109 tests passing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@JanProvaznik JanProvaznik force-pushed the threadsafe-analyzer-v2 branch from 39edd10 to 70b47ee Compare March 24, 2026 11:03
Removes Microsoft.NET.Test.Sdk, xunit.runner.visualstudio, and other
packages that are implicitly referenced by the .NET SDK. These cause
NETSDK1023 errors in the repo's SDK (10.0.104) which treats this as
an error, not a warning.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@JanProvaznik JanProvaznik force-pushed the threadsafe-analyzer-v2 branch 2 times, most recently from 6842561 to 460144b Compare March 24, 2026 15:32
Adds a Roslyn analyzer (MSBuildTask0001-0005) that detects unsafe API
usage in MSBuild task implementations for multithreaded execution.

Analyzer project uses central package management. Test project opts
out (ManagePackageVersionsCentrally=false) due to NuGet version
conflicts in CodeAnalysis.Testing transitive dependencies.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@JanProvaznik JanProvaznik force-pushed the threadsafe-analyzer-v2 branch from 460144b to 3feb591 Compare March 24, 2026 15:32
@JanProvaznik
Copy link
Copy Markdown
Member Author

Analyzer Findings on MSBuild Tasks (/p:BuildAnalyzer=true)

15 unique issues across 6 task files. These are real thread-safety issues to fix in follow-up PRs.

MSBuildTask0002 — TaskEnvironment Replacement Required (10)

File Line API Fix
Copy.cs 118 Environment.GetEnvironmentVariable("MSBuildCopyParallelCount") TaskEnvironment.GetEnvironmentVariable()
Copy.cs 123 Environment.GetEnvironmentVariable("MSBUILDALWAYSRETRY") TaskEnvironment.GetEnvironmentVariable()
Copy.cs 241 Environment.GetEnvironmentVariable("MSBUILDALWAYSOVERWRITEREADONLYFILES") TaskEnvironment.GetEnvironmentVariable()
AssignTargetPath.cs 85 Path.GetFullPath(item) TaskEnvironment.GetAbsolutePath()
AssignTargetPath.cs 146 Path.GetFullPath(rootDir) TaskEnvironment.GetAbsolutePath()
FindUnderPath.cs 76 Path.GetFullPath(Path.Value) TaskEnvironment.GetAbsolutePath()
FindUnderPath.cs 107 Path.GetFullPath(item.ItemSpec) TaskEnvironment.GetAbsolutePath()
Unzip.cs 179 Path.GetFullPath(...) destination TaskEnvironment.GetAbsolutePath()
Unzip.cs 189 Path.GetFullPath(...) zip-slip check TaskEnvironment.GetAbsolutePath()

MSBuildTask0003 — File API Requires Absolute Path (1)

File Line API
GetFileHash.cs 148 File.OpenRead(filePath) — wrap with TaskEnvironment.GetAbsolutePath()

MSBuildTask0005 — Transitive Unsafe API in Call Chain (4)

File Line Chain Unsafe API
Copy.cs 209 IsMatchingSizeAndTimeStamp → FileState.get_Length → FileDirInfo.ThrowFileInfoException new FileInfo(string)
Copy.cs 261 CopyFileWithLogging → FileState.Reset → FileDirInfo..ctor new DirectoryInfo(string)
Move.cs 92 Execute → MoveFileWithLogging → NativeMethods.MoveFileEx File.Move, File.Delete, File.GetAttributes

Recommended Migration Order

  1. Copy.cs (High) — most-used task, already IMultiThreadableTask
  2. AssignTargetPath.cs (Medium) — 2 Path.GetFullPath calls
  3. FindUnderPath.cs (Medium) — 2 Path.GetFullPath calls
  4. Unzip.cs (Medium) — 2 Path.GetFullPath calls
  5. Move.cs (Low) — transitive, paths typically absolute
  6. GetFileHash.cs (Low) — 1 File.OpenRead, path typically absolute

- All diagnostics are Warning (removed dual Info/Warning severity)
- Added WarningsNotAsErrors in Tasks.csproj so analyzer warnings
  don't break WarnAsError builds while issues are being fixed
- Enabled /p:BuildAnalyzer=true in Linux Core Multithreaded Mode CI job
- Removed issues.md (posted as PR comment instead)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@JanProvaznik JanProvaznik marked this pull request as ready for review March 24, 2026 15:48
@JanProvaznik JanProvaznik requested a review from a team as a code owner March 24, 2026 15:48
Copilot AI review requested due to automatic review settings March 24, 2026 15:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a new Roslyn analyzer + code fix package to help MSBuild task authors identify (and in some cases automatically migrate away from) APIs that are unsafe under multithreaded task execution (IMultiThreadableTask), including a transitive call-chain analyzer to surface unsafe APIs reached through helper methods.

Changes:

  • Added ThreadSafeTaskAnalyzer analyzer project (direct API rules + shared helpers + transitive call-chain rule) and integrated it into MSBuild.slnx.
  • Added a code fix provider for MSBuildTask0002 (TaskEnvironment replacements) and MSBuildTask0003 (absolute path wrapping).
  • Added a new analyzer test project and enabled running the analyzer on Microsoft.Build.Tasks when /p:BuildAnalyzer=true (including CI).

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/ThreadSafeTaskAnalyzer/TransitiveCallChainAnalyzer.cs Implements compilation-wide call graph scan + BFS to report transitive unsafe API usage (MSBuildTask0005).
src/ThreadSafeTaskAnalyzer/SharedAnalyzerHelpers.cs Shared banned API lookup + path-argument safety detection helpers.
src/ThreadSafeTaskAnalyzer/MultiThreadableTaskAnalyzer.cs Direct per-type analyzer for MSBuildTask0001–0004, scoped via RegisterSymbolStartAction.
src/ThreadSafeTaskAnalyzer/MultiThreadableTaskCodeFixProvider.cs Code fixes for MSBuildTask0002/0003.
src/ThreadSafeTaskAnalyzer/BannedApiDefinitions.cs Defines banned API sets resolved via documentation IDs.
src/ThreadSafeTaskAnalyzer/DiagnosticDescriptors.cs Defines diagnostic descriptors for MSBuildTask0001–0005.
src/ThreadSafeTaskAnalyzer/DiagnosticIds.cs Adds public IDs for the analyzer rules.
src/ThreadSafeTaskAnalyzer/README.md Documents rule set, scope, fixes, usage, and architecture.
src/ThreadSafeTaskAnalyzer/AnalyzerReleases.Unshipped.md Tracks new analyzer rules for Roslyn release tracking.
src/ThreadSafeTaskAnalyzer/AnalyzerReleases.Shipped.md Initializes shipped release tracking file.
src/ThreadSafeTaskAnalyzer/ThreadSafeTaskAnalyzer.csproj New analyzer project definition + Roslyn package references.
src/ThreadSafeTaskAnalyzer.Tests/TestHelpers.cs Test harness that compiles sources with framework stubs and runs analyzers.
src/ThreadSafeTaskAnalyzer.Tests/MultiThreadableTaskAnalyzerTests.cs Large unit test suite validating direct analyzer behavior and safe patterns.
src/ThreadSafeTaskAnalyzer.Tests/TransitiveCallChainAnalyzerTests.cs Unit tests validating MSBuildTask0005 transitive detection and chain formatting.
src/ThreadSafeTaskAnalyzer.Tests/MultiThreadableTaskCodeFixProviderTests.cs Code-fix transformation tests via CSharpCodeFixTest.
src/ThreadSafeTaskAnalyzer.Tests/WriteAllTextDetailedTest.cs Regression-style test validating path diagnostic count for WriteAllText.
src/ThreadSafeTaskAnalyzer.Tests/ThreadSafeTaskAnalyzer.Tests.csproj New test project configuration and package references.
src/Tasks/Microsoft.Build.Tasks.csproj Adds conditional analyzer reference (BuildAnalyzer) + relaxes analyzer IDs from WarnAsError.
eng/dependabot/Directory.Packages.props Adds Roslyn authoring package versions for central management.
MSBuild.slnx Includes the new analyzer and analyzer test projects in the main solution.
.vsts-dotnet-ci.yml Enables /p:BuildAnalyzer=true in the bootstrapped -mt CI leg.

CI fixes:
- SA1133: Split combined attributes in CodeFixProvider
- SA1111: Move closing parens to last parameter lines
- SA1508: Remove blank line before closing brace

Review fixes:
- Remove pinned LangVersion 12.0 (repo default is latest)
- Update RS2001 suppression comment
- Remove MSBuildTask0001 from WarningsNotAsErrors (it's Error, not Warning)
- Make FindTaskTypes recursive for deeply nested types
- Fix README: remove dual-severity claims, clarify performance notes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@JanProvaznik JanProvaznik self-assigned this Mar 25, 2026
Copy link
Copy Markdown
Member

@AR-May AR-May left a comment

Choose a reason for hiding this comment

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

Looks good to go. Most my comments are just suggestions.

JanProvaznik and others added 2 commits March 27, 2026 15:45
- Clarify [MSBuildMultiThreadableTaskAnalyzed] attribute purpose in README
- Simplify AnalyzerReleases.Shipped.md format
- Improve MSBuildTask0003 message to mention shared working directory
- Extract shared constants to WellKnownTypeNames.cs
- Add doc comment explaining MaxCallChainDepth purpose
- Deduplicate scope option reading into SharedAnalyzerHelpers
- Convert 3 similar transitive tests to Theory with InlineData
- Add scope option tests (multithreadable_only vs all)
- Add TestAnalyzerConfigOptionsProvider for scope testing

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

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@JanProvaznik JanProvaznik merged commit ee7d635 into dotnet:main Mar 30, 2026
10 checks passed
dfederm pushed a commit to dfederm/msbuild that referenced this pull request Apr 9, 2026
## Thread-Safe Task Analyzer

Adds a Roslyn analyzer that detects unsafe API usage in MSBuild task
implementations, guiding task authors toward thread-safe patterns
required for multithreaded task execution (`IMultiThreadableTask`).

### Diagnostic Rules

| ID | Default Severity | Title |
|----|-----------------|-------|
| **MSBuildTask0001** | Error | API is never safe in MSBuild tasks
(Console.*, Environment.Exit, ThreadPool, etc.) |
| **MSBuildTask0002** | Warning / Info ¹ | API requires TaskEnvironment
alternative |
| **MSBuildTask0003** | Warning / Info ¹ | File system API requires
absolute path |
| **MSBuildTask0004** | Warning / Info ¹ | API may cause issues
(Assembly.Load, Activator, etc.) |
| **MSBuildTask0005** | Warning / Info ¹ | Transitive unsafe API in call
chain (source-level BFS) |

¹ Warning for `IMultiThreadableTask` / `[MSBuildMultiThreadableTask]`,
Info for plain `ITask`. Users can configure via `.editorconfig`.

### What's included
- **Core analyzer** (`MultiThreadableTaskAnalyzer`): Per-type scoped
analysis via `RegisterSymbolStartAction`
- **Transitive analyzer** (`TransitiveCallChainAnalyzer`): Source-level
BFS call graph traces unsafe APIs through helper methods
- **Code fix provider**: Auto-fixes for MSBuildTask0002 (TaskEnvironment
replacements) and MSBuildTask0003 (`GetAbsolutePath` wrapping)
- **Shared helpers** (`SharedAnalyzerHelpers`): Deduplicated path safety
analysis and banned API resolution
- **109 unit tests** passing

### Build integration
- Added to `MSBuild.slnx`
- `/p:BuildAnalyzer=true` enables analyzer on Tasks.csproj
- Produces 56 warnings on Tasks project (34 env + 12 path + 10
transitive), 0 false positives

### Safe pattern recognition (zero false positives verified)
- `ITaskItem.GetMetadata("FullPath")`
- `Path.Combine(absolutePath, ...)`
- `Path.GetDirectoryName(absolutePath)`
- `AbsolutePath` type (implicit conversion)
- `File.WriteAllText(path, contents)` — only path param flagged

### What's NOT included (preserved in `threadsafe-analyzer` branch)
- IL decompiler (`ILCallGraphExtender.cs`) — 1,011 lines of raw IL byte
parsing with 6 verified opcode bugs
- Demo project (~1,600 lines)
- Cross-assembly IL tests (~870 lines)

### Testing
- Stress-tested on MSBuild Tasks project, external task libraries, and
SDK-style task patterns
- Zero false positives across all test scenarios

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

3 participants