C#: Guard against cyclic inclusions in project files

This commit is contained in:
Tom Hvitved
2018-11-22 11:25:13 +01:00
parent e4f68ae324
commit c3ccdfa7f9
3 changed files with 52 additions and 17 deletions

View File

@@ -1020,5 +1020,27 @@ namespace Semmle.Extraction.Tests
var autobuilder = CreateAutoBuilder("csharp", false);
TestAutobuilderScript(autobuilder, 0, 4);
}
[Fact]
public void TestCyclicDirsProj()
{
Actions.FileExists["dirs.proj"] = true;
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.FileExists["csharp.log"] = false;
Actions.EnumerateFiles[@"C:\Project"] = "dirs.proj";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var dirsproj1 = new XmlDocument();
dirsproj1.LoadXml(@"<Project DefaultTargets=""Build"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"" ToolsVersion=""3.5"">
<ItemGroup>
<ProjectFiles Include=""dirs.proj"" />
</ItemGroup>
</Project>");
Actions.LoadXml["dirs.proj"] = dirsproj1;
var autobuilder = CreateAutoBuilder("csharp", false);
TestAutobuilderScript(autobuilder, 1, 0);
}
}
}

View File

@@ -23,13 +23,13 @@ namespace Semmle.Autobuild
public Version ToolsVersion { get; private set; }
readonly List<Project> includedProjects = new List<Project>();
public override IEnumerable<IProjectOrSolution> IncludedProjects =>
includedProjects.Concat(includedProjects.SelectMany(s => s.IncludedProjects));
readonly Lazy<List<Project>> includedProjectsLazy;
public override IEnumerable<IProjectOrSolution> IncludedProjects => includedProjectsLazy.Value;
public Project(Autobuilder builder, string path) : base(builder, path)
{
ToolsVersion = new Version();
includedProjectsLazy = new Lazy<List<Project>>(() => new List<Project>());
if (!builder.Actions.FileExists(FullPath))
return;
@@ -69,17 +69,22 @@ namespace Semmle.Autobuild
}
}
// The documentation on `.proj` files is very limited, but it appears that both
// `<ProjectFile Include="X"/>` and `<ProjectFiles Include="X"/>` is valid
var mgr = new XmlNamespaceManager(projFile.NameTable);
mgr.AddNamespace("msbuild", "http://schemas.microsoft.com/developer/msbuild/2003");
var projectFileIncludes = root.SelectNodes("//msbuild:Project/msbuild:ItemGroup/msbuild:ProjectFile/@Include", mgr).OfType<XmlNode>();
var projectFilesIncludes = root.SelectNodes("//msbuild:Project/msbuild:ItemGroup/msbuild:ProjectFiles/@Include", mgr).OfType<XmlNode>();
foreach (var include in projectFileIncludes.Concat(projectFilesIncludes))
includedProjectsLazy = new Lazy<List<Project>>(() =>
{
var includePath = builder.Actions.IsWindows() ? include.Value : include.Value.Replace("\\", "/");
includedProjects.Add(new Project(builder, builder.Actions.PathCombine(Path.GetDirectoryName(this.FullPath), includePath)));
}
var ret = new List<Project>();
// The documentation on `.proj` files is very limited, but it appears that both
// `<ProjectFile Include="X"/>` and `<ProjectFiles Include="X"/>` is valid
var mgr = new XmlNamespaceManager(projFile.NameTable);
mgr.AddNamespace("msbuild", "http://schemas.microsoft.com/developer/msbuild/2003");
var projectFileIncludes = root.SelectNodes("//msbuild:Project/msbuild:ItemGroup/msbuild:ProjectFile/@Include", mgr).OfType<XmlNode>();
var projectFilesIncludes = root.SelectNodes("//msbuild:Project/msbuild:ItemGroup/msbuild:ProjectFiles/@Include", mgr).OfType<XmlNode>();
foreach (var include in projectFileIncludes.Concat(projectFilesIncludes))
{
var includePath = builder.Actions.IsWindows() ? include.Value : include.Value.Replace("\\", "/");
ret.Add(new Project(builder, builder.Actions.PathCombine(Path.GetDirectoryName(this.FullPath), includePath)));
}
return ret;
});
}
}
}

View File

@@ -15,7 +15,7 @@ namespace Semmle.Autobuild
string FullPath { get; }
/// <summary>
/// Gets a list of other projects included by this file.
/// Gets a list of other projects directly included by this file.
/// </summary>
IEnumerable<IProjectOrSolution> IncludedProjects { get; }
}
@@ -39,8 +39,16 @@ namespace Semmle.Autobuild
/// <summary>
/// Holds if this file includes a project with code from language <paramref name="l"/>.
/// </summary>
public static bool HasLanguage(this IProjectOrSolution p, Language l) =>
l.ProjectFileHasThisLanguage(p.FullPath) ||
p.IncludedProjects.Any(p0 => l.ProjectFileHasThisLanguage(p0.FullPath));
public static bool HasLanguage(this IProjectOrSolution p, Language l)
{
bool HasLanguage(IProjectOrSolution p0, HashSet<string> seen)
{
if (seen.Contains(p0.FullPath))
return false;
seen.Add(p0.FullPath); // guard against cyclic includes
return l.ProjectFileHasThisLanguage(p0.FullPath) || p0.IncludedProjects.Any(p1 => HasLanguage(p1, seen));
}
return HasLanguage(p, new HashSet<string>());
}
}
}