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
53 changes: 53 additions & 0 deletions src/Build.UnitTests/Evaluation/Expander_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,59 @@ public void ExpandAllIntoStringLeaveEscapedComplex()
expander.ExpandIntoStringLeaveEscaped(xmlattribute.Value, ExpanderOptions.ExpandAll, MockElementLocation.Instance));
}

/// <summary>
/// Exercises ExpandIntoStringAndUnescape and ExpanderOptions.Truncate
/// </summary>
[Fact]
public void ExpandAllIntoStringTruncated()
{
ProjectInstance project = ProjectHelpers.CreateEmptyProjectInstance();
var manySpaces = "".PadLeft(2000);
var pg = new PropertyDictionary<ProjectPropertyInstance>();
pg.Set(ProjectPropertyInstance.Create("ManySpacesProperty", manySpaces));
var itemMetadataTable = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "ManySpacesMetadata", manySpaces }
};
var itemMetadata = new StringMetadataTable(itemMetadataTable);
var projectItemGroups = new ItemDictionary<ProjectItemInstance>();
var itemGroup = new List<ProjectItemInstance>();
for (int i = 0; i < 50; i++)
{
var item = new ProjectItemInstance(project, "ManyItems", $"ThisIsAFairlyLongFileName_{i}.bmp", project.FullPath);
item.SetMetadata("Foo", $"ThisIsAFairlyLongMetadataValue_{i}");
itemGroup.Add(item);
}
var lookup = new Lookup(projectItemGroups, pg);
lookup.EnterScope("x");
lookup.PopulateWithItems("ManySpacesItem", new []
{
new ProjectItemInstance (project, "ManySpacesItem", "Foo", project.FullPath),
new ProjectItemInstance (project, "ManySpacesItem", manySpaces, project.FullPath),
new ProjectItemInstance (project, "ManySpacesItem", "Bar", project.FullPath),
});
lookup.PopulateWithItems("Exactly1024", new[]
{
new ProjectItemInstance (project, "Exactly1024", "".PadLeft(1024), project.FullPath),
new ProjectItemInstance (project, "Exactly1024", "Foo", project.FullPath),
});
lookup.PopulateWithItems("ManyItems", itemGroup);

Expander<ProjectPropertyInstance, ProjectItemInstance> expander = new Expander<ProjectPropertyInstance, ProjectItemInstance>(lookup, lookup, itemMetadata, FileSystems.Default);

XmlAttribute xmlattribute = (new XmlDocument()).CreateAttribute("dummy");
xmlattribute.Value = "'%(ManySpacesMetadata)' != '' and '$(ManySpacesProperty)' != '' and '@(ManySpacesItem)' != '' and '@(Exactly1024)' != '' and '@(ManyItems)' != '' and '@(ManyItems->'%(Foo)')' != ''";

var expected =
$"'{"",1021}...' != '' and " +
$"'{"",1021}...' != '' and " +
$"'Foo;{"",1017}...' != '' and " +
$"'{"",1024};...' != '' and " +
"'ThisIsAFairlyLongFileName_0.bmp;ThisIsAFairlyLongFileName_1.bmp;ThisIsAFairlyLongFileName_2.bmp;...' != '' and " +
"'ThisIsAFairlyLongMetadataValue_0;ThisIsAFairlyLongMetadataValue_1;ThisIsAFairlyLongMetadataValue_2;...' != ''";
Assert.Equal(expected, expander.ExpandIntoStringAndUnescape(xmlattribute.Value, ExpanderOptions.ExpandAll | ExpanderOptions.Truncate, MockElementLocation.Instance));
}

/// <summary>
/// Exercises ExpandAllIntoString with a string that does not need expanding.
/// In this case the expanded string should be reference identical to the passed in string.
Expand Down
2 changes: 1 addition & 1 deletion src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ internal List<TargetSpecification> GetDependencies(ProjectLoggingContext project
if (!projectLoggingContext.LoggingService.OnlyLogCriticalEvents)
{
// Expand the expression for the Log. Since we know the condition evaluated to false, leave unexpandable properties in the condition so as not to cause an error
string expanded = _expander.ExpandIntoStringAndUnescape(_target.Condition, ExpanderOptions.ExpandPropertiesAndItems | ExpanderOptions.LeavePropertiesUnexpandedOnError, _target.ConditionLocation);
string expanded = _expander.ExpandIntoStringAndUnescape(_target.Condition, ExpanderOptions.ExpandPropertiesAndItems | ExpanderOptions.LeavePropertiesUnexpandedOnError | ExpanderOptions.Truncate, _target.ConditionLocation);

// By design: Not building dependencies. This is what NAnt does too.
// NOTE: In the original code, this was logged from the target logging context. However, the target
Expand Down
2 changes: 1 addition & 1 deletion src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ private void LogSkippedTask(ItemBucket bucket, TaskExecutionMode howToExecuteTas
if (!_targetLoggingContext.LoggingService.OnlyLogCriticalEvents)
{
// Expand the expression for the Log. Since we know the condition evaluated to false, leave unexpandable properties in the condition so as not to cause an error
string expanded = bucket.Expander.ExpandIntoStringAndUnescape(_targetChildInstance.Condition, ExpanderOptions.ExpandAll | ExpanderOptions.LeavePropertiesUnexpandedOnError, _targetChildInstance.ConditionLocation);
string expanded = bucket.Expander.ExpandIntoStringAndUnescape(_targetChildInstance.Condition, ExpanderOptions.ExpandAll | ExpanderOptions.LeavePropertiesUnexpandedOnError | ExpanderOptions.Truncate, _targetChildInstance.ConditionLocation);

// Whilst we are within the processing of the task, we haven't actually started executing it, so
// our skip task message needs to be in the context of the target. However any errors should be reported
Expand Down
2 changes: 1 addition & 1 deletion src/Build/Evaluation/Evaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1753,7 +1753,7 @@ private void ExpandAndLoadImportsFromUnescapedImportExpressionConditioned(
if (_logProjectImportedEvents)
{
// Expand the expression for the Log. Since we know the condition evaluated to false, leave unexpandable properties in the condition so as not to cause an error
string expanded = _expander.ExpandIntoStringAndUnescape(importElement.Condition, ExpanderOptions.ExpandProperties | ExpanderOptions.LeavePropertiesUnexpandedOnError, importElement.ConditionLocation);
string expanded = _expander.ExpandIntoStringAndUnescape(importElement.Condition, ExpanderOptions.ExpandProperties | ExpanderOptions.LeavePropertiesUnexpandedOnError | ExpanderOptions.Truncate, importElement.ConditionLocation);

ProjectImportedEventArgs eventArgs = new ProjectImportedEventArgs(
importElement.Location.Line,
Expand Down
63 changes: 61 additions & 2 deletions src/Build/Evaluation/Expander.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ internal enum ExpanderOptions
/// </remarks>
LeavePropertiesUnexpandedOnError = 0x20,

/// <summary>
/// When an expansion occurs, truncate it to Expander.DefaultTruncationCharacterLimit or Expander.DefaultTruncationItemLimit.
/// </summary>
Truncate = 0x40,

/// <summary>
/// Expand only properties and then item lists
/// </summary>
Expand Down Expand Up @@ -119,6 +124,16 @@ internal class Expander<P, I>
where P : class, IProperty
where I : class, IItem
{
/// <summary>
/// A limit for truncating string expansions within an evaluated Condition. Properties, item metadata, or item groups will be truncated to N characters such as 'N...'.
/// Enabled by ExpanderOptions.Truncate.
/// </summary>
private const int CharacterLimitPerExpansion = 1024;
/// <summary>
/// A limit for truncating string expansions for item groups within an evaluated Condition. N items will be evaluated such as 'A;B;C;...'.
/// Enabled by ExpanderOptions.Truncate.
/// </summary>
private const int ItemLimitPerExpansion = 3;
private static readonly char[] s_singleQuoteChar = { '\'' };
private static readonly char[] s_backtickChar = { '`' };
private static readonly char[] s_doubleQuoteChar = { '"' };
Expand Down Expand Up @@ -463,6 +478,14 @@ private static bool IsValidPropertyName(string propertyName)
return true;
}

/// <summary>
/// Returns true if ExpanderOptions.Truncate is set and EscapeHatches.DoNotTruncateConditions is not set
/// </summary>
private static bool IsTruncationEnabled(ExpanderOptions options)
{
return (options & ExpanderOptions.Truncate) != 0 && !Traits.Instance.EscapeHatches.DoNotTruncateConditions;
}

/// <summary>
/// Scan for the closing bracket that matches the one we've already skipped;
/// essentially, pushes and pops on a stack of parentheses to do this.
Expand Down Expand Up @@ -841,7 +864,7 @@ private class MetadataMatchEvaluator
internal MetadataMatchEvaluator(IMetadataTable metadata, ExpanderOptions options)
{
_metadata = metadata;
_options = (options & ExpanderOptions.ExpandMetadata);
_options = options & (ExpanderOptions.ExpandMetadata | ExpanderOptions.Truncate);

ErrorUtilities.VerifyThrow(options != ExpanderOptions.Invalid, "Must be expanding metadata of some kind");
}
Expand Down Expand Up @@ -873,6 +896,10 @@ internal string ExpandSingleMetadata(Match itemMetadataMatch)
)
{
metadataValue = _metadata.GetEscapedValue(itemType, metadataName);
if (IsTruncationEnabled(_options) && metadataValue.Length > CharacterLimitPerExpansion)
{
metadataValue = metadataValue.Substring(0, CharacterLimitPerExpansion - 3) + "...";
}
}

return metadataValue;
Expand Down Expand Up @@ -1089,6 +1116,15 @@ internal static object ExpandPropertiesLeaveTypedAndEscaped(
propertyValue = LookupProperty(properties, expression, propertyStartIndex + 2, propertyEndIndex - 1, elementLocation, usedUninitializedProperties);
}

if (IsTruncationEnabled(options) && propertyValue != null)
{
var value = propertyValue.ToString();
if (value.Length > CharacterLimitPerExpansion)
{
propertyValue = value.Substring(0, CharacterLimitPerExpansion - 3) + "...";
}
}

// Record our result, and advance
// our sourceIndex pointer to the character just after the closing
// parenthesis.
Expand Down Expand Up @@ -2022,9 +2058,32 @@ ExpanderOptions options
return true;
}

int startLength = builder.Length;
bool truncate = IsTruncationEnabled(options);

// if the capture.Separator is not null, then ExpandExpressionCapture would have joined the items using that separator itself
foreach (var item in itemsFromCapture)
for (int i = 0; i < itemsFromCapture.Count; i++)
{
var item = itemsFromCapture[i];
if (truncate)
{
if (i >= ItemLimitPerExpansion)
{
builder.Append("...");
return false;
}
int currentLength = builder.Length - startLength;
if (currentLength + item.Key.Length > CharacterLimitPerExpansion)
{
int truncateIndex = CharacterLimitPerExpansion - currentLength - 3;
if (truncateIndex > 0)
{
builder.Append(item.Key, 0, truncateIndex);
}
builder.Append("...");
return false;
}
}
builder.Append(item.Key);
builder.Append(';');
}
Expand Down
5 changes: 5 additions & 0 deletions src/Shared/Traits.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ internal class EscapeHatches
/// </summary>
public readonly bool TruncateTaskInputs = Environment.GetEnvironmentVariable("MSBUILDTRUNCATETASKINPUTS") == "1";

/// <summary>
/// Disables truncation of Condition messages in Tasks/Targets via ExpanderOptions.Truncate.
/// </summary>
public readonly bool DoNotTruncateConditions = Environment.GetEnvironmentVariable("MSBuildDoNotTruncateConditions") == "1";

/// <summary>
/// Emit events for project imports.
/// </summary>
Expand Down