Skip to content

ShouldTreatWarningAsError in OOP TaskHost checks wrong collection (NRE + silent failure) #13438

@JanProvaznik

Description

@JanProvaznik

Summary

OutOfProcTaskHostNode.ShouldTreatWarningAsError() checks WarningsAsMessages instead of WarningsAsErrors when matching specific warning codes. This means <MSBuildWarningsAsErrors>CS0618</MSBuildWarningsAsErrors> silently fails to promote warnings to errors when the task runs out-of-process via TaskHostFactory.

Introduced in: v17.10.0 (PR #7309)
Affected file: src/MSBuild/OutOfProcTaskHostNode.cs

Semantics (confusing but intentional)

WarningsAsErrors = null           → feature disabled
WarningsAsErrors = {} (empty set) → ALL warnings become errors (universal)
WarningsAsErrors = {"CS0618"}     → only CS0618 becomes error (specific)

Empty set = "all" is the established convention — see LoggingService.cs:2010:

"If the list is empty or contains the code, the warning should be treated as an error"

Bug

// OutOfProcTaskHostNode.cs — BUGGY (main)
public bool ShouldTreatWarningAsError(string warningCode)
{
    if (WarningsAsErrors == null || WarningsAsMessages?.Contains(warningCode) == true)
        return false;

    return (WarningsAsErrors.Count == 0 && WarningAsErrorNotOverriden(warningCode))
        || WarningsAsMessages.Contains(warningCode);
//          ^^^^^^^^^^^^^^^^^ should be WarningsAsErrors
}

Compare the canonical correct implementation in LoggingService.cs:2011:

(WarningsAsErrors.Count == 0 && WarningAsErrorNotOverriden(warningEvent))
    || WarningsAsErrors.Contains(warningEvent.Code)
//     ^^^^^^^^^^^^^^^^ correct collection

Trace: specific codes (e.g. WarningsAsErrors = {"CS0618"}, task emits CS0618)

Step Expression Value Note
1 WarningsAsErrors == null false set has 1 item
2 WarningsAsMessages?.Contains("CS0618") false or null CS0618 not in messages
3 → don't return false, continue
4 WarningsAsErrors.Count == 0 false Count is 1, not 0
5 → first operand of || is false
6 WarningsAsMessages.Contains("CS0618") NRE or false Wrong collection!
7 → returns false BUG: should return true

Step 6 hits the wrong collection. If WarningsAsMessages is null → NRE crash. If non-null → always false here (step 2 already verified the code isn't in messages). Either way, specific-code matching never works.

Trace: treat-all (e.g. WarningsAsErrors = {}, task emits MY0001)

The empty-set path was also confirmed broken in testing (warning not promoted). The WarningsAsMessages.Contains() on the return line likely crashes or returns false before the Count == 0 short-circuit can help, depending on null state of WarningsAsMessages. Even if it doesn't NRE, the overall method is unreliable for OOP TaskHost scenarios.

Impact

Verified on .NET SDK 10.0.101 (public release):

Scenario Expected Actual (main)
In-proc + specific codes ✅ Error ✅ Error
OOP + specific codes (MY0001) ✅ Error ❌ NRE crash (MSB4018)
OOP + treat-all (-warnaserror) ✅ Error ❌ Warning (not promoted)

Both OOP paths are broken. The NRE crash (not a silent pass) is the more common scenario since most users don't set <MSBuildWarningsAsMessages>.

NRE stack trace (shipped MSBuild)

error MSB4018: The "EmitWarningTask" task failed unexpectedly.
System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.Build.CommandLine.OutOfProcTaskHostNode.ShouldTreatWarningAsError(String warningCode)

Repro

See attached repro.zip. Steps:

cd WarnTask && dotnet build && cd ..

# Control — in-proc promotes correctly (build FAILS):
dotnet msbuild TestInProc.proj -t:Build

# Bug — OOP TaskHost does NOT promote (build SUCCEEDS):
dotnet msbuild TestOOP.proj -t:Build

Fix

- return (WarningsAsErrors.Count == 0 && WarningAsErrorNotOverriden(warningCode))
-     || WarningsAsMessages.Contains(warningCode);
+ return (warningsAsErrors.Count == 0 && WarningAsErrorNotOverriden(warningCode))
+     || warningsAsErrors.Contains(warningCode);

repro.zip

Metadata

Metadata

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions