Skip to content

MSBuildTask0005 cannot be suppressed at the unsafe call site, only on the task's Execute method #14788

Description

@ViktorHofer

Summary

MSBuildTask0005 ("transitively calls unsafe API") is reported on the task's Execute method rather than at the call site of the unsafe API. That makes it impossible to suppress narrowly: a #pragma warning disable MSBuildTask0005 around the actual offending call has no effect, so the only way to silence a single reviewed call is to disable the rule across the task's entire Execute call graph — which also blinds every other transitive violation in that task.

Repro

In dotnet/dotnet, Microsoft.DotNet.UnifiedBuild.Tasks has a shared ProcessService helper that runs a child process with a timeout:

catch (OperationCanceledException)
{
#pragma warning disable MSBuildTask0005   // <-- has no effect
    try { process.Kill(entireProcessTree: true); } catch { }
#pragma warning restore MSBuildTask0005
    throw new TimeoutException($"Process timed out after {Timeout.TotalMilliseconds} ms");
}

The diagnostic is still emitted, at a completely different file/line:

CreateSourceArtifact.cs(70,26): warning MSBuildTask0005: 'CreateSourceArtifact.Execute' transitively
calls unsafe API 'Process.Kill(bool)' via: CreateSourceArtifact.Execute → CreateSourceArtifact.ExecuteAsync
→ ProcessService.RunProcessAsync → Process.Kill(bool)

To get a clean build we had to suppress on Execute itself:

#pragma warning disable MSBuildTask0005
    public override bool Execute() => ExecuteAsync().GetAwaiter().GetResult();
#pragma warning restore MSBuildTask0005

which now hides all transitive violations for that task, including future regressions — the opposite of what the rule is for.

Why this particular call is safe

Process.Kill(entireProcessTree: true) here targets a process the task itself started. entireProcessTree walks descendants, so it cannot reach the MSBuild host, which is the parent. Removing the kill is strictly worse: a timed-out child would be leaked, holding file handles and potentially stalling the build.

So this is a case where the call is legitimately safe and needs a targeted, reviewed suppression.

Requests

  1. Honor #pragma warning disable at the unsafe call site for MSBuildTask0005, in addition to (or instead of) the reporting location. This is the main ask.
  2. Alternatively/additionally, support an opt-out attribute on the helper method, e.g. [MSBuildTaskSafeApiUsage("Process.Kill", Justification = "...")], so the review is recorded next to the code being reviewed.
  3. Consider also reporting a second, lower-severity diagnostic at the unsafe call site, so the location is discoverable without reading the call chain in the message.

Impact

Any task that funnels process or file work through a shared helper hits this. The workaround (suppressing on Execute) actively removes analyzer coverage, which undermines using MSBuildTask0005 as a regression guard.

Found while migrating the dotnet/dotnet VMR's Microsoft.DotNet.UnifiedBuild.Tasks to the multithreaded task model.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions