Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/Build.UnitTests/BackEnd/StringArrayWithNullsTask.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

#nullable disable

namespace Microsoft.Build.UnitTests.BackEnd
{
/// <summary>
/// A task that returns a string array containing null elements.
/// Used to test the fix for https://github.com/dotnet/msbuild/issues/13174
/// </summary>
public class StringArrayWithNullsTask : Task
{
[Output]
public string[] OutputArray { get; set; }

[Output]
public int Pid { get; set; }

public override bool Execute()
{
// Return an array with nulls - this pattern occurs in real tasks like GenerateGlobalUsings
OutputArray = new string[] { "first", null, "third", null, "fifth" };
Pid = Process.GetCurrentProcess().Id;
return true;
}
}
}
40 changes: 40 additions & 0 deletions src/Build.UnitTests/BackEnd/TaskHostFactory_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -363,5 +363,45 @@ public void VariousParameterTypesCanBeTransmittedToAndReceivedFromTaskHost()
projectInstance.GetPropertyValue("CustomStructOutput").ShouldBe(TaskBuilderTestTask.s_customStruct.ToString(CultureInfo.InvariantCulture));
projectInstance.GetPropertyValue("EnumOutput").ShouldBe(TargetBuiltReason.BeforeTargets.ToString());
}

/// <summary>
/// Verifies that a task returning a string[] with null elements does not crash
/// when executed via TaskHostFactory. This is a regression test for
/// https://github.com/dotnet/msbuild/issues/13174
/// </summary>
[Fact]
public void StringArrayWithNullsDoesNotCrashTaskHost()
{
using TestEnvironment env = TestEnvironment.Create();

string projectContents = $@"
<Project>
<UsingTask TaskName=""{typeof(StringArrayWithNullsTask).FullName}"" AssemblyFile=""{AssemblyLocation}"" TaskFactory=""TaskHostFactory"" />
<Target Name=""TestTarget"">
<{typeof(StringArrayWithNullsTask).Name}>
<Output ItemName=""OutputItems"" TaskParameter=""OutputArray"" />
<Output PropertyName=""TaskPid"" TaskParameter=""Pid"" />
</{typeof(StringArrayWithNullsTask).Name}>
</Target>
</Project>";

TransientTestFile project = env.CreateFile("testProject.csproj", projectContents);
ProjectInstance projectInstance = new(project.Path);
BuildManager buildManager = BuildManager.DefaultBuildManager;
BuildResult buildResult = buildManager.Build(new BuildParameters(), new BuildRequestData(projectInstance, targetsToBuild: new[] { "TestTarget" }));

// The build should succeed - nulls should be filtered, not cause a crash
buildResult.OverallResult.ShouldBe(BuildResultCode.Success);

// Verify task ran out-of-process (TaskHostFactory should force this)
string taskPidStr = projectInstance.GetPropertyValue("TaskPid");
taskPidStr.ShouldNotBeNullOrEmpty();
int.TryParse(taskPidStr, out int taskPid).ShouldBeTrue();
Process.GetCurrentProcess().Id.ShouldNotBe(taskPid, "Task should have run in a separate TaskHost process");

// Verify output items - nulls should be filtered out, leaving 3 items
var outputItems = projectInstance.GetItems("OutputItems");
outputItems.Count.ShouldBe(3, "Null elements should be filtered from the string array");
}
}
}
52 changes: 51 additions & 1 deletion src/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,16 @@ private OutOfProcTaskHostTaskResult InstantiateAndExecuteTask(
{
try
{
finalParameterValues[value.Name] = value.GetValue(wrappedTask, null);
object outputValue = value.GetValue(wrappedTask, null);

// Filter null elements from string[] outputs to avoid crash.
// See https://github.com/dotnet/msbuild/issues/13174
if (outputValue is string[] stringArray)
{
outputValue = FilterNullsFromStringArray(stringArray, value.Name);
}
Comment thread
JanProvaznik marked this conversation as resolved.

finalParameterValues[value.Name] = outputValue;
}
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
{
Expand Down Expand Up @@ -451,5 +460,46 @@ private void LogErrorDelegate(string taskLocation, int taskLine, int taskColumn,
null,
taskName));
}

/// <summary>
/// Filters null elements from a string[] task output.
/// See https://github.com/dotnet/msbuild/issues/13174
/// </summary>
private string[] FilterNullsFromStringArray(string[] stringArray, string parameterName)
{
// Count nulls
int nullCount = 0;
foreach (string s in stringArray)
{
if (s == null)
{
nullCount++;
}
}
Comment thread
JanProvaznik marked this conversation as resolved.
Comment thread
JanProvaznik marked this conversation as resolved.

if (nullCount == 0)
{
return stringArray;
}

// Filter nulls and log
string[] filtered = new string[stringArray.Length - nullCount];
int j = 0;
foreach (string s in stringArray)
{
if (s != null)
{
filtered[j++] = s;
}
}
Comment thread
JanProvaznik marked this conversation as resolved.

buildEngine.LogMessageEvent(new BuildMessageEventArgs(
message: ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("TaskHostAcquired_NullsFiltered", parameterName, nullCount),
helpKeyword: null,
senderName: taskName,
importance: MessageImportance.Normal));
Comment thread
JanProvaznik marked this conversation as resolved.
Comment thread
JanProvaznik marked this conversation as resolved.

return filtered;
}
}
}
7 changes: 7 additions & 0 deletions src/MSBuild/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1719,6 +1719,13 @@
</data>
<!-- **** MSBUILD_LOGGING_ARGS strings end **** -->

<!-- **** TaskHost strings begin **** -->
<data name="TaskHostAcquired_NullsFiltered" xml:space="preserve">
<value>Task output parameter "{0}" contained {1} null element(s) which have been filtered out.</value>
<comment>LOCALIZATION: {0} is the name of the task output parameter, {1} is the count of null elements filtered.</comment>
</data>
<!-- **** TaskHost strings end **** -->

<!--
The command line message bucket is: MSB1001 - MSB1999

Expand Down
5 changes: 5 additions & 0 deletions src/MSBuild/Resources/xlf/Strings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/MSBuild/Resources/xlf/Strings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/MSBuild/Resources/xlf/Strings.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/MSBuild/Resources/xlf/Strings.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/MSBuild/Resources/xlf/Strings.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/MSBuild/Resources/xlf/Strings.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/MSBuild/Resources/xlf/Strings.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/MSBuild/Resources/xlf/Strings.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/MSBuild/Resources/xlf/Strings.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/MSBuild/Resources/xlf/Strings.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/MSBuild/Resources/xlf/Strings.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.