Skip to content

Analyze base classes of multithreadable tasks in TaskAnalyzer - #14831

Draft
ViktorHofer with Copilot wants to merge 2 commits into
mainfrom
copilot/fix-msbuildtask0002-reporting
Draft

Analyze base classes of multithreadable tasks in TaskAnalyzer#14831
ViktorHofer with Copilot wants to merge 2 commits into
mainfrom
copilot/fix-msbuildtask0002-reporting

Conversation

Copilot AI commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Context

MSBuildTask0002/0003/0005 were silent when the offending code lived in an unannotated base class of a task carrying [MSBuildMultiThreadableTask]. Inherited members run on the shared node exactly like declared ones, so this failed in the worst possible way for a migration aid: the analyzer reported clean, and the violation surfaced later as a nondeterministic CI failure.

public abstract class BaseWithEnv : Task
{
    protected string Token => Environment.GetEnvironmentVariable("SYSTEM_ACCESSTOKEN"); // not reported
    public override bool Execute() { Log.LogMessage(Token); return ExecuteCore(); }
    protected abstract bool ExecuteCore();
}

[MSBuildMultiThreadableTask]
public sealed class DerivedAnnotated : BaseWithEnv, IMultiThreadableTask { /* clean build */ }

Confirmed in the field on dotnet/arcade's AkaMSLinksBase, where File.ReadAllText(ClientCertificate) went unreported while both derived tasks analyzed clean.

Two independent causes:

  1. MultiThreadableTaskAnalyzer decides scope per named type. Under scope = multithreadable_only, a base class is neither annotated nor otherwise in scope, so reportEnvironmentRules was false for it.
  2. TransitiveCallChainAnalyzer seeded its BFS from taskType.GetMembers(), which returns declared members. A base-declared Execute() was never a seed, and FindTaskTypes additionally skips abstract types.

This is why the issue's same-class private static helper reported but the inherited case did not — analysis stopped at the type boundary, not the method boundary.

Changes Made

  • SharedAnalyzerHelpers — added IsMultiThreadable, CollectMultiThreadableBaseTypes (walks the base chain of every annotated task in the compilation), and EnumerateTypes/EnumerateNestedTypes.
  • MultiThreadableTaskAnalyzer — computes that set once per compilation via Lazy<T> and treats membership as analyzeAsMultiThreadable, so the direct rules fire on inherited code. Cost per symbol callback is one ImmutableHashSet.Contains.
  • TransitiveCallChainAnalyzer — seeds the BFS from each task type and its source-declared base chain, deduplicated so a base shared by several tasks is analyzed once rather than once per derived task.
  • Docssrc/TaskAnalyzer/README.md gains an "Inherited Code" subsection and a scope-table row; the MT-migration skill no longer documents this as a permanent blind spot.

Diagnostics are reported at the declaration site in the base, which is where the offending code lives.

Two subtleties: type.BaseType returns the constructed symbol (Base<string>) while symbol callbacks use the definition (Base<T>), so both walks normalize with .OriginalDefinition; and the walk stops at types with no DeclaringSyntaxReferences, since a referenced assembly can never derive from a source type.

Testing

Nine tests covering unannotated base, grandparent, generic base, non-ITask base, shared base reported once, and negative cases for a plain task's base. The seven positive tests were verified to fail with the product change reverted.

Rebuilt src/Tasks with -p:BuildAnalyzer=true — the configuration the "Linux Core Multithreaded Mode" job uses — before and after. 0001/0002/0003/0006/0007 are unchanged and nothing disappeared; MSBuildTask0005 goes 288 → 322. The 34 new lines are 17 distinct GenerateManifestBase.Execute chains across two TFMs: an abstract Task, IMultiThreadableTask base that was never seeded before, now reporting real File.Copy/File.Open/File.Delete reachability. MSBuildTask0005 is in that project's WarningsNotAsErrors.

Notes

The suggested interim mitigation — an informational diagnostic when the base is outside the compilation — is deliberately not included. Every task derives from metadata Task/ToolTask, so it would fire on essentially every annotated task, and new diagnostics are a breaking change under this repo's policy. Worth revisiting as opt-in if the cross-assembly case bites in practice; that limitation is now documented rather than silent.

#14772 still covers 0006/0007 base-class behavior. #14791 (task inputs flowing through IFileSystem/ICommandFactory abstractions) is untouched — that one needs a different rule shape, not a scope fix.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Hello @copilot, I noticed that you’re changing an .swr file or any file under src/Package/MSBuild.VSSetup.. Please make sure to validate this change by an experimental VS insertion. This is accomplished by pushing to an exp/* branch, which requires write permissions to this repo.

Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix MSBuildTask0002 reporting for inherited code Analyze base classes of multithreadable tasks in TaskAnalyzer Aug 25, 2026
Copilot AI requested a review from ViktorHofer August 25, 2026 14:28
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.

TaskAnalyzer: MSBuildTask0002 is not reported for code inherited from an unannotated base class

2 participants