Skip to content

Recognize AbsolutePath.Value as an absolute path in task analyzers - #15073

Open
AR-May wants to merge 2 commits into
dotnet:mainfrom
AR-May:ar-may-analyzer-absolutepath-value
Open

AR-May wants to merge 2 commits into
dotnet:mainfrom
AR-May:ar-may-analyzer-absolutepath-value

Conversation

@AR-May

@AR-May AR-May commented Sep 18, 2026

Copy link
Copy Markdown
Member

Fixes #15072

Context

Passing AbsolutePath.Value to file APIs incorrectly produces MSBuildTask0003 or transitive MSBuildTask0005, despite the value already being absolute.

Changes Made

  • Recognize the instance Value property of the resolved AbsolutePath type, including through existing local-initializer tracing.
  • Use framework type identity for safe Path helpers so same-named lookalikes cannot qualify. Resolve the symbol once per analyzer compilation.
  • Add direct/transitive regression coverage and a short README example.

Compatibility

No changes to analyzer activation or diagnostic severities. Existing canonicalization behavior is preserved; unrelated Value properties and lookalike Path helpers remain untrusted.

AR-May and others added 2 commits September 18, 2026 11:02
Accept the resolved AbsolutePath.Value property as an absolute-path source. Resolve the trusted framework Path once per analyzer compilation, independent of return type, and pass its symbol through canonicalization and recursive safe-path checks. Resolve once before the code-fix argument scan as well. Preserve original comments and document the safe pattern concisely.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Cover direct and transitive file calls, local initializers, unrelated or transformed values, and source/metadata Path lookalikes. Include nested Path calls and initializer chains. All snippets are compiler-valid and the complete analyzer suite passes 384 cases.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@AR-May
AR-May marked this pull request as ready for review September 18, 2026 13:00
Copilot AI lite review requested due to automatic review settings September 18, 2026 13:00
@AR-May
AR-May deployed to copilot-pat-pool September 18, 2026 13:00 — with GitHub Actions Active
@AR-May
AR-May deployed to copilot-pat-pool September 18, 2026 13:00 — with GitHub Actions Active
@AR-May
AR-May deployed to copilot-pat-pool September 18, 2026 13:01 — with GitHub Actions Active

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.

🟡 Changes recommended

The typed-parameter analyzer still trusts display-name Path lookalikes in two branches.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Updates MSBuild task analyzers to recognize AbsolutePath.Value as safe and validate framework System.IO.Path identity.

Changes:

  • Added AbsolutePath.Value safety recognition and local-flow support.
  • Propagated framework Path symbols through analyzers and code fixes.
  • Added regression tests and README documentation.
File summaries
File Description
src/TaskAnalyzer/SharedAnalyzerHelpers.cs Implements value and framework-Path recognition.
src/TaskAnalyzer/MultiThreadableTaskAnalyzer.cs Applies safety checks to direct analysis.
src/TaskAnalyzer/TransitiveCallChainAnalyzer.cs Applies safety checks to transitive analysis.
src/TaskAnalyzer/PreferTypedParameterAnalyzer.cs Threads path identity into typed-parameter analysis.
src/TaskAnalyzer/MultiThreadableTaskCodeFixProvider.cs Uses updated safety checks for fixes.
src/TaskAnalyzer/README.md Documents AbsolutePath.Value.
src/TaskAnalyzer.Tests/MultiThreadableTaskAnalyzerTests.cs Adds direct and lookalike-path coverage.
src/TaskAnalyzer.Tests/TransitiveCallChainAnalyzerTests.cs Adds transitive-flow coverage.
Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +713 to +714
INamedTypeSymbol? directoryInfoType,
INamedTypeSymbol? systemIOPathType)
@AR-May
AR-May deployed to copilot-pat-pool September 18, 2026 13:18 — with GitHub Actions Active
@AR-May
AR-May deployed to copilot-pat-pool September 18, 2026 13:19 — with GitHub Actions Active

@github-actions github-actions Bot 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.

# Dimension Verdict
11 Cross-Platform Correctness 🟡 1 MAJOR
17 File I/O & Path Handling 🟡 1 MAJOR
22 Correctness & Edge Cases 🟡 1 MAJOR

✅ 21/24 dimensions clean.

  • Cross-Platform Correctness — resolve framework System.IO.Path across split contract assemblies
  • File I/O & Path Handling — prevent drive-relative Path.Combine components from being treated as absolute
  • Correctness & Edge Cases — account for local reassignment before propagating AbsolutePath.Value safety

Warning

Firewall blocked 4 domains

The following domains were blocked by the firewall during workflow execution:

  • api.github.com
  • github.com
  • learn.microsoft.com
  • raw.githubusercontent.com

[!TIP]
api.github.com is blocked because GitHub API access uses the built-in GitHub tools by default. Instead of adding api.github.com to network.allowed, use tools.github.mode: gh-proxy for direct pre-authenticated GitHub CLI access without requiring network access to api.github.com:

tools:
  github:
    mode: gh-proxy

See GitHub Tools for more information on gh-proxy mode.

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "api.github.com"
    - "github.com"
    - "learn.microsoft.com"
    - "raw.githubusercontent.com"

See Network Configuration for more information.

Generated by Expert Code Review (on open) for #15073 · copilot · gpt56 · 837 AIC · ⌖ 4.57 AIC · ⊞ 17.8K

{
// Resolve Path from the core library, not a source or referenced lookalike.
var coreLibrary = compilation.GetSpecialType(SpecialType.System_Object).ContainingAssembly;
return coreLibrary?.GetTypeByMetadataName("System.IO.Path");

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.

[MAJOR] Cross-Platform Correctness

A contract-reference compilation can define System.Object in System.Runtime while System.IO.Path is defined in or forwarded through System.Runtime.Extensions. In that case coreLibrary.GetTypeByMetadataName("System.IO.Path") returns null, so every real Path.GetDirectoryName/Combine/GetFullPath call fails IsSystemIOPath and valid absolute-path code starts producing MSBuildTask0003 (including warnings-as-errors builds). The current tests use runtime implementation assemblies, where both types happen to resolve from System.Private.CoreLib, so they do not cover this layout.

Recommendation: resolve System.IO.Path from referenced assembly symbols (excluding the source assembly/lookalikes), deduplicate forwarded symbols with SymbolEqualityComparer.Default, and require one framework match. Add a Roslyn compilation test with split contract references (System.Runtime + System.Runtime.Extensions).

IsSystemIOPath(invocation, systemIOPathType) &&
invocation.Arguments.Length >= 2 &&
IsWrappedSafely(invocation.Arguments[0].Value, taskEnvironmentType, absolutePathType, iTaskItemType))
IsWrappedSafely(invocation.Arguments[0].Value, taskEnvironmentType, absolutePathType, iTaskItemType, systemIOPathType))

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.

[MAJOR] File I/O & Path Handling

This now treats Path.Combine(path.Value, @"C:relative.txt") as safe. On Windows the second component is drive-rooted but not fully qualified, and Path.Combine(@"D:\\safe", @"C:relative.txt") yields C:relative.txt; the file API then resolves it against drive C's current directory. Before this PR, AbsolutePath.Value did not satisfy the recursive check, so MSBuildTask0003 was retained.

AbsolutePath path = TaskEnvironment.GetAbsolutePath(@"D:\\safe");
File.OpenRead(Path.Combine(path.Value, @"C:relative.txt"));

Recommendation: do not propagate safety from the first Combine argument unless every later component is proven unable to reset the root; at minimum reject rooted/drive-relative constant components. Add this Windows regression case.

}

// AbsolutePath.Value is already fully qualified; no canonicalization is required.
if (IsAbsolutePathValue(operation, absolutePathType))

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.

[MAJOR] Correctness & Edge Cases

Marking AbsolutePath.Value safe exposes the existing initializer-only local tracing to stale values. IsWrappedSafely follows the declaration initializer but never checks later assignments, so this concrete relative access is now incorrectly accepted:

AbsolutePath absolutePath = TaskEnvironment.GetAbsolutePath("safe.txt");
string path = absolutePath.Value;
path = "relative.txt";
File.OpenRead(path); // MSBuildTask0003 is suppressed

The base behavior reported this because .Value was not considered safe.

Recommendation: only propagate safety through a local when data-flow/reaching-definition analysis proves the initializer still reaches the use (or conservatively stop propagation when the local has another write), and add this reassignment regression test.

@AR-May
AR-May deployed to copilot-pat-pool September 18, 2026 13:20 — with GitHub Actions Active
@AR-May
AR-May requested a review from VolPlita September 18, 2026 13:20
public TaskEnvironment TaskEnvironment { get; set; }
public override bool Execute()
{
new FileInfo(TaskEnvironment.GetAbsolutePath("foo.txt").Value);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I can see how this pattern isn't wrong but is it one we want to encourage? Isn't avoiding the need for this why we did the silent cast-to-string support?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yes, I also was not sure whether we would like to encourage this. On the other hand, we, while already had the silent cast - i believe it was there from the first version, used Value ourselves and I think customers would do that as well. And it is not too wrong to explicitly do.

@baronfel what do you think?

@AR-May AR-May self-assigned this Sep 21, 2026
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.

Task analyzer reports false positives for AbsolutePath.Value

4 participants