Skip to content
25 changes: 20 additions & 5 deletions src/Utilities/TrackedDependencies/CanonicalTrackedInputFiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,9 @@ private void RemoveDependencyFromEntry(string rootingMarker, ITaskItem dependenc
/// <param name="correspondingOutputs">Outputs that correspond ot the sources (used for same file processing)</param>
public void RemoveDependenciesFromEntryIfMissing(ITaskItem[] source, ITaskItem[] correspondingOutputs)
{
// Cache of files that have been checked and exist.
Dictionary<string, bool> fileCache = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);

if (correspondingOutputs != null)
{
ErrorUtilities.VerifyThrowArgument(source.Length == correspondingOutputs.Length, "Tracking_SourcesAndCorrespondingOutputMismatch");
Expand All @@ -1041,23 +1044,24 @@ public void RemoveDependenciesFromEntryIfMissing(ITaskItem[] source, ITaskItem[]
// construct a combined root marker for the sources and outputs to remove from the graph
string rootingMarker = FileTracker.FormatRootingMarker(source, correspondingOutputs);

RemoveDependenciesFromEntryIfMissing(rootingMarker);
RemoveDependenciesFromEntryIfMissing(rootingMarker, fileCache);

// Remove entries for each individual source
for (int sourceIndex = 0; sourceIndex < source.Length; sourceIndex++)
{
rootingMarker = correspondingOutputs != null
? FileTracker.FormatRootingMarker(source[sourceIndex], correspondingOutputs[sourceIndex])
: FileTracker.FormatRootingMarker(source[sourceIndex]);
RemoveDependenciesFromEntryIfMissing(rootingMarker);
RemoveDependenciesFromEntryIfMissing(rootingMarker, fileCache);
}
}

/// <summary>
/// Remove the output graph entries for the given rooting marker
/// </summary>
/// <param name="rootingMarker"></param>
private void RemoveDependenciesFromEntryIfMissing(string rootingMarker)
/// <param name="fileCache">The cache used to store whether each file exists or not.</param>
private void RemoveDependenciesFromEntryIfMissing(string rootingMarker, Dictionary<string, bool> fileCache)
Comment thread
benvillalobos marked this conversation as resolved.
{
// In the event of incomplete tracking information (i.e. this root was not present), just continue quietly
// as the user could have killed the tool being tracked, or another error occurred during its execution.
Expand All @@ -1070,8 +1074,19 @@ private void RemoveDependenciesFromEntryIfMissing(string rootingMarker)
{
if (keyIndex++ > 0)
{
// If we are ignoring missing files, then only record those that exist
if (FileUtilities.FileExistsNoThrow(file))
// Record whether or not each file exists and cache it.
// We do this to save time (On^2), at the expense of data O(n).
bool inFileCache = fileCache.TryGetValue(file, out bool fileExists);

// Have we cached the file yet? If not, cache whether or not it exists.
if (!inFileCache)
{
fileExists = FileUtilities.FileExistsNoThrow(file);
fileCache.Add(file, fileExists);
}

// Does the cached file exist?
if (fileExists)
{
dependenciesWithoutMissingFiles.Add(file, dependencies[file]);
}
Expand Down
37 changes: 23 additions & 14 deletions src/Utilities/TrackedDependencies/CanonicalTrackedOutputFiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,9 @@ private void RemoveDependencyFromEntry(string rootingMarker, ITaskItem dependenc
/// <param name="correspondingOutputs">Outputs that correspond ot the sources (used for same file processing)</param>
public void RemoveDependenciesFromEntryIfMissing(ITaskItem[] source, ITaskItem[] correspondingOutputs)
{
// Cache of files and whether or not they exist.
Dictionary<string, bool> fileCache = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);

if (correspondingOutputs != null)
{
ErrorUtilities.VerifyThrowArgument(source.Length == correspondingOutputs.Length, "Tracking_SourcesAndCorrespondingOutputMismatch");
Expand All @@ -732,29 +735,24 @@ public void RemoveDependenciesFromEntryIfMissing(ITaskItem[] source, ITaskItem[]
// construct a combined root marker for the sources and outputs to remove from the graph
string rootingMarker = FileTracker.FormatRootingMarker(source, correspondingOutputs);

RemoveDependenciesFromEntryIfMissing(rootingMarker);
RemoveDependenciesFromEntryIfMissing(rootingMarker, fileCache);

// Remove entries for each individual source
for (int sourceIndex = 0; sourceIndex < source.Length; sourceIndex++)
{
if (correspondingOutputs != null)
{
rootingMarker = FileTracker.FormatRootingMarker(source[sourceIndex], correspondingOutputs[sourceIndex]);
}
else
{
rootingMarker = FileTracker.FormatRootingMarker(source[sourceIndex]);
}

RemoveDependenciesFromEntryIfMissing(rootingMarker);
rootingMarker = correspondingOutputs != null
? FileTracker.FormatRootingMarker(source[sourceIndex], correspondingOutputs[sourceIndex])
: FileTracker.FormatRootingMarker(source[sourceIndex]);
Comment on lines +743 to +745
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rootingMarker = correspondingOutputs != null
? FileTracker.FormatRootingMarker(source[sourceIndex], correspondingOutputs[sourceIndex])
: FileTracker.FormatRootingMarker(source[sourceIndex]);
rootingMarker = FileTracker.FormatRootingMarker(source[sourceIndex], correspondingOutputs?[sourceIndex]);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't take this suggestion, it causes a nullreferenceexception. FormatRootingMarker is capable of handling the null array case, but this suggestion passes an array with 1 null element. In this case I'd rather keep the original.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, what? This should never be passing an array, just null or the value of the array. Did you miss the '?'?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you may be mistaken as to which overload gets called here. There are two overloads that are very similar:
public static string FormatRootingMarker(ITaskItem source, ITaskItem output) and
public static string FormatRootingMarker(ITaskItem[] sources, ITaskItem[] outputs)

The suggested change calls the first overload, which packs both items into arrays like so:

public static string FormatRootingMarker(ITaskItem source, ITaskItem output) => FormatRootingMarker(new[] { source }, new[] { output });

Then calls the second overload which eventually does this:

// So we don't have to deal with null checks.
            outputs = outputs ?? Array.Empty<ITaskItem>();

            var rootSources = new List<string>(sources.Length + outputs.Length);

            foreach (ITaskItem source in sources)
            {
                rootSources.Add(FileUtilities.NormalizePath(source.ItemSpec).ToUpperInvariant());
            }

            foreach (ITaskItem output in outputs)
            {
                rootSources.Add(FileUtilities.NormalizePath(output.ItemSpec).ToUpperInvariant());
            }

Notice it does a null check on the array, not the items within. and the second foreach loop will break because it tries to get the ItemSpec from a null item.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I understand why the change could be problematic, but now I don't understand why your version would work. In any case, it's the same as it had been before, and the change wasn't really an optimization anyway, so I'm not going to block on this.

RemoveDependenciesFromEntryIfMissing(rootingMarker, fileCache);
}
}

/// <summary>
/// Remove the output graph entries for the given rooting marker
/// </summary>
/// <param name="rootingMarker"></param>
private void RemoveDependenciesFromEntryIfMissing(string rootingMarker)
/// <param name="fileCache">The cache used to store whether each file exists or not.</param>
private void RemoveDependenciesFromEntryIfMissing(string rootingMarker, Dictionary<string, bool> fileCache)
{
// In the event of incomplete tracking information (i.e. this root was not present), just continue quietly
// as the user could have killed the tool being tracked, or another error occurred during its execution.
Expand All @@ -767,8 +765,19 @@ private void RemoveDependenciesFromEntryIfMissing(string rootingMarker)
{
if (keyIndex++ > 0)
{
// If we are ignoring missing files, then only record those that exist
if (FileUtilities.FileExistsNoThrow(file))
// Record whether or not each file exists and cache it.
// We do this to save time (On^2), at the expense of data O(n).
bool inFileCache = fileCache.TryGetValue(file, out bool fileExists);

// Have we cached the file yet? If not, cache its existence.
if (!inFileCache)
{
fileExists = FileUtilities.FileExistsNoThrow(file);
fileCache.Add(file, fileExists);
}

// Does the cached file exist?
if (fileExists)
{
dependenciesWithoutMissingFiles.Add(file, dependencies[file]);
}
Expand Down