зеркало из https://github.com/github/codeql.git
Merge pull request #295 from calumgrant/cs/extractor/open-source
C#: Open-source extractor
This commit is contained in:
Коммит
68dae60927
|
@ -0,0 +1,13 @@
|
|||
obj/
|
||||
TestResults/
|
||||
*.manifest
|
||||
*.pdb
|
||||
*.suo
|
||||
*.mdb
|
||||
*.vsmdi
|
||||
csharp.log
|
||||
**/bin/Debug
|
||||
**/bin/Release
|
||||
*.tlog
|
||||
.vs
|
||||
*.user
|
|
@ -0,0 +1,83 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27130.2036
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Semmle.Util", "Semmle.Util\Semmle.Util.csproj", "{CDD7AD69-0FD8-40F0-A9DA-F1077A2A85D6}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Semmle.Extraction", "Semmle.Extraction\Semmle.Extraction.csproj", "{81EAAD75-4BE1-44E4-91DF-20778216DB64}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Semmle.Extraction.CSharp", "Semmle.Extraction.CSharp\Semmle.Extraction.CSharp.csproj", "{C4D62DA0-B64B-440B-86DC-AB52318CB8BF}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Semmle.Extraction.CIL", "Semmle.Extraction.CIL\Semmle.Extraction.CIL.csproj", "{399A1579-68F0-40F4-9A23-F241BA697F9C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Semmle.Autobuild", "Semmle.Autobuild\Semmle.Autobuild.csproj", "{5131EF00-0BA9-4436-A3B0-C5CDAB4B194C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Semmle.Extraction.CSharp.Standalone", "Semmle.Extraction.CSharp.Standalone\Semmle.Extraction.CSharp.Standalone.csproj", "{D00E7D25-0FA0-48EC-B048-CD60CE1B30D8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Semmle.Extraction.CIL.Driver", "Semmle.Extraction.CIL.Driver\Semmle.Extraction.CIL.Driver.csproj", "{EFA400B3-C1CE-446F-A4E2-8B44E61EF47C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Semmle.Extraction.CSharp.Driver", "Semmle.Extraction.CSharp.Driver\Semmle.Extraction.CSharp.Driver.csproj", "{C36453BF-0C82-448A-B15D-26947503A2D3}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Semmle.Extraction.Tests", "Semmle.Extraction.Tests\Semmle.Extraction.Tests.csproj", "{CD8D3F90-AD2E-4BB5-8E82-B94AA293864A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Semmle.Util.Tests", "Semmle.Util.Tests\Semmle.Util.Tests.csproj", "{55A620F0-23F6-440D-A5BA-0567613B3C0F}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Semmle.Autobuild.Tests", "Semmle.Autobuild.Tests\Semmle.Autobuild.Tests.csproj", "{CE267461-D762-4F53-A275-685A0A4EC48D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{CDD7AD69-0FD8-40F0-A9DA-F1077A2A85D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CDD7AD69-0FD8-40F0-A9DA-F1077A2A85D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CDD7AD69-0FD8-40F0-A9DA-F1077A2A85D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CDD7AD69-0FD8-40F0-A9DA-F1077A2A85D6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{81EAAD75-4BE1-44E4-91DF-20778216DB64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{81EAAD75-4BE1-44E4-91DF-20778216DB64}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{81EAAD75-4BE1-44E4-91DF-20778216DB64}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{81EAAD75-4BE1-44E4-91DF-20778216DB64}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C4D62DA0-B64B-440B-86DC-AB52318CB8BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C4D62DA0-B64B-440B-86DC-AB52318CB8BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C4D62DA0-B64B-440B-86DC-AB52318CB8BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C4D62DA0-B64B-440B-86DC-AB52318CB8BF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{399A1579-68F0-40F4-9A23-F241BA697F9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{399A1579-68F0-40F4-9A23-F241BA697F9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{399A1579-68F0-40F4-9A23-F241BA697F9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{399A1579-68F0-40F4-9A23-F241BA697F9C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5131EF00-0BA9-4436-A3B0-C5CDAB4B194C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5131EF00-0BA9-4436-A3B0-C5CDAB4B194C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5131EF00-0BA9-4436-A3B0-C5CDAB4B194C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5131EF00-0BA9-4436-A3B0-C5CDAB4B194C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D00E7D25-0FA0-48EC-B048-CD60CE1B30D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D00E7D25-0FA0-48EC-B048-CD60CE1B30D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D00E7D25-0FA0-48EC-B048-CD60CE1B30D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D00E7D25-0FA0-48EC-B048-CD60CE1B30D8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EFA400B3-C1CE-446F-A4E2-8B44E61EF47C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EFA400B3-C1CE-446F-A4E2-8B44E61EF47C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EFA400B3-C1CE-446F-A4E2-8B44E61EF47C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EFA400B3-C1CE-446F-A4E2-8B44E61EF47C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C36453BF-0C82-448A-B15D-26947503A2D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C36453BF-0C82-448A-B15D-26947503A2D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C36453BF-0C82-448A-B15D-26947503A2D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C36453BF-0C82-448A-B15D-26947503A2D3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CD8D3F90-AD2E-4BB5-8E82-B94AA293864A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CD8D3F90-AD2E-4BB5-8E82-B94AA293864A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CD8D3F90-AD2E-4BB5-8E82-B94AA293864A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{55A620F0-23F6-440D-A5BA-0567613B3C0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{55A620F0-23F6-440D-A5BA-0567613B3C0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{55A620F0-23F6-440D-A5BA-0567613B3C0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CE267461-D762-4F53-A275-685A0A4EC48D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CE267461-D762-4F53-A275-685A0A4EC48D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CE267461-D762-4F53-A275-685A0A4EC48D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CE267461-D762-4F53-A275-685A0A4EC48D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {E2B2BAC0-D55C-45DB-8CB3-8CEBA86FB547}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,883 @@
|
|||
using Xunit;
|
||||
using Semmle.Autobuild;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Build.Construction;
|
||||
|
||||
namespace Semmle.Extraction.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test class to script Autobuilder scenarios.
|
||||
/// For most methods, it uses two fields:
|
||||
/// - an IList to capture the the arguments passed to it
|
||||
/// - an IDictionary of possible return values.
|
||||
/// </summary>
|
||||
class TestActions : IBuildActions
|
||||
{
|
||||
/// <summary>
|
||||
/// List of strings passed to FileDelete.
|
||||
/// </summary>
|
||||
public IList<string> FileDeleteIn = new List<string>();
|
||||
|
||||
void IBuildActions.FileDelete(string file)
|
||||
{
|
||||
FileDeleteIn.Add(file);
|
||||
}
|
||||
|
||||
public IList<string> FileExistsIn = new List<string>();
|
||||
public IDictionary<string, bool> FileExists = new Dictionary<string, bool>();
|
||||
|
||||
bool IBuildActions.FileExists(string file)
|
||||
{
|
||||
FileExistsIn.Add(file);
|
||||
if (FileExists.TryGetValue(file, out var ret))
|
||||
return ret;
|
||||
throw new ArgumentException("Missing FileExists " + file);
|
||||
}
|
||||
|
||||
public IList<string> RunProcessIn = new List<string>();
|
||||
public IDictionary<string, int> RunProcess = new Dictionary<string, int>();
|
||||
public IDictionary<string, string> RunProcessOut = new Dictionary<string, string>();
|
||||
public IDictionary<string, string> RunProcessWorkingDirectory = new Dictionary<string, string>();
|
||||
|
||||
int IBuildActions.RunProcess(string cmd, string args, string workingDirectory, IDictionary<string, string> env, out IList<string> stdOut)
|
||||
{
|
||||
var pattern = cmd + " " + args;
|
||||
RunProcessIn.Add(pattern);
|
||||
if (RunProcessOut.TryGetValue(pattern, out var str))
|
||||
stdOut = str.Split("\n");
|
||||
else
|
||||
throw new ArgumentException("Missing RunProcessOut " + pattern);
|
||||
RunProcessWorkingDirectory.TryGetValue(pattern, out var wd);
|
||||
if (wd != workingDirectory)
|
||||
throw new ArgumentException("Missing RunProcessWorkingDirectory " + pattern);
|
||||
if (RunProcess.TryGetValue(pattern, out var ret))
|
||||
return ret;
|
||||
throw new ArgumentException("Missing RunProcess " + pattern);
|
||||
}
|
||||
|
||||
int IBuildActions.RunProcess(string cmd, string args, string workingDirectory, IDictionary<string, string> env)
|
||||
{
|
||||
var pattern = cmd + " " + args;
|
||||
RunProcessIn.Add(pattern);
|
||||
RunProcessWorkingDirectory.TryGetValue(pattern, out var wd);
|
||||
if (wd != workingDirectory)
|
||||
throw new ArgumentException("Missing RunProcessWorkingDirectory " + pattern);
|
||||
if (RunProcess.TryGetValue(pattern, out var ret))
|
||||
return ret;
|
||||
throw new ArgumentException("Missing RunProcess " + pattern);
|
||||
}
|
||||
|
||||
public IList<string> DirectoryDeleteIn = new List<string>();
|
||||
|
||||
void IBuildActions.DirectoryDelete(string dir, bool recursive)
|
||||
{
|
||||
DirectoryDeleteIn.Add(dir);
|
||||
}
|
||||
|
||||
public IDictionary<string, bool> DirectoryExists = new Dictionary<string, bool>();
|
||||
public IList<string> DirectoryExistsIn = new List<string>();
|
||||
|
||||
bool IBuildActions.DirectoryExists(string dir)
|
||||
{
|
||||
DirectoryExistsIn.Add(dir);
|
||||
if (DirectoryExists.TryGetValue(dir, out var ret))
|
||||
return ret;
|
||||
throw new ArgumentException("Missing DirectoryExists " + dir);
|
||||
}
|
||||
|
||||
public IDictionary<string, string> GetEnvironmentVariable = new Dictionary<string, string>();
|
||||
|
||||
string IBuildActions.GetEnvironmentVariable(string name)
|
||||
{
|
||||
if (GetEnvironmentVariable.TryGetValue(name, out var ret))
|
||||
return ret;
|
||||
throw new ArgumentException("Missing GetEnvironmentVariable " + name);
|
||||
}
|
||||
|
||||
public string GetCurrentDirectory;
|
||||
|
||||
string IBuildActions.GetCurrentDirectory()
|
||||
{
|
||||
return GetCurrentDirectory;
|
||||
}
|
||||
|
||||
public IDictionary<string, string> EnumerateFiles = new Dictionary<string, string>();
|
||||
|
||||
IEnumerable<string> IBuildActions.EnumerateFiles(string dir)
|
||||
{
|
||||
if (EnumerateFiles.TryGetValue(dir, out var str))
|
||||
return str.Split("\n");
|
||||
throw new ArgumentException("Missing EnumerateFiles " + dir);
|
||||
}
|
||||
|
||||
public IDictionary<string, string> EnumerateDirectories = new Dictionary<string, string>();
|
||||
|
||||
IEnumerable<string> IBuildActions.EnumerateDirectories(string dir)
|
||||
{
|
||||
if (EnumerateDirectories.TryGetValue(dir, out var str))
|
||||
return string.IsNullOrEmpty(str) ? Enumerable.Empty<string>() : str.Split("\n");
|
||||
throw new ArgumentException("Missing EnumerateDirectories " + dir);
|
||||
}
|
||||
|
||||
public bool IsWindows;
|
||||
|
||||
bool IBuildActions.IsWindows() => IsWindows;
|
||||
|
||||
string IBuildActions.PathCombine(params string[] parts)
|
||||
{
|
||||
return string.Join('\\', parts);
|
||||
}
|
||||
|
||||
void IBuildActions.WriteAllText(string filename, string contents)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A fake solution to build.
|
||||
/// </summary>
|
||||
class TestSolution : ISolution
|
||||
{
|
||||
public IEnumerable<Project> Projects => throw new NotImplementedException();
|
||||
|
||||
public IEnumerable<SolutionConfigurationInSolution> Configurations => throw new NotImplementedException();
|
||||
|
||||
public string DefaultConfigurationName => "Release";
|
||||
|
||||
public string DefaultPlatformName => "x86";
|
||||
|
||||
public string Path { get; set; }
|
||||
|
||||
public int ProjectCount => throw new NotImplementedException();
|
||||
|
||||
public Version ToolsVersion => new Version("14.0");
|
||||
|
||||
public TestSolution(string path)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
}
|
||||
|
||||
public class BuildScriptTests
|
||||
{
|
||||
TestActions Actions = new TestActions();
|
||||
|
||||
// Records the arguments passed to StartCallback.
|
||||
IList<string> StartCallbackIn = new List<string>();
|
||||
|
||||
void StartCallback(string s)
|
||||
{
|
||||
StartCallbackIn.Add(s);
|
||||
}
|
||||
|
||||
// Records the arguments passed to EndCallback
|
||||
IList<string> EndCallbackIn = new List<string>();
|
||||
IList<int> EndCallbackReturn = new List<int>();
|
||||
|
||||
void EndCallback(int ret, string s)
|
||||
{
|
||||
EndCallbackReturn.Add(ret);
|
||||
EndCallbackIn.Add(s);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestBuildCommand()
|
||||
{
|
||||
var cmd = BuildScript.Create("abc", "def ghi", null, null);
|
||||
|
||||
Actions.RunProcess["abc def ghi"] = 1;
|
||||
cmd.Run(Actions, StartCallback, EndCallback);
|
||||
Assert.Equal("abc def ghi", Actions.RunProcessIn[0]);
|
||||
Assert.Equal("abc def ghi", StartCallbackIn[0]);
|
||||
Assert.Equal("", EndCallbackIn[0]);
|
||||
Assert.Equal(1, EndCallbackReturn[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestAnd1()
|
||||
{
|
||||
var cmd = BuildScript.Create("abc", "def ghi", null, null) & BuildScript.Create("odasa", null, null, null);
|
||||
|
||||
Actions.RunProcess["abc def ghi"] = 1;
|
||||
cmd.Run(Actions, StartCallback, EndCallback);
|
||||
|
||||
Assert.Equal("abc def ghi", Actions.RunProcessIn[0]);
|
||||
Assert.Equal("abc def ghi", StartCallbackIn[0]);
|
||||
Assert.Equal("", EndCallbackIn[0]);
|
||||
Assert.Equal(1, EndCallbackReturn[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestAnd2()
|
||||
{
|
||||
var cmd = BuildScript.Create("odasa", null, null, null) & BuildScript.Create("abc", "def ghi", null, null);
|
||||
|
||||
Actions.RunProcess["abc def ghi"] = 1;
|
||||
Actions.RunProcess["odasa "] = 0;
|
||||
cmd.Run(Actions, StartCallback, EndCallback);
|
||||
|
||||
Assert.Equal("odasa ", Actions.RunProcessIn[0]);
|
||||
Assert.Equal("odasa ", StartCallbackIn[0]);
|
||||
Assert.Equal("", EndCallbackIn[0]);
|
||||
Assert.Equal(0, EndCallbackReturn[0]);
|
||||
|
||||
Assert.Equal("abc def ghi", Actions.RunProcessIn[1]);
|
||||
Assert.Equal("abc def ghi", StartCallbackIn[1]);
|
||||
Assert.Equal("", EndCallbackIn[1]);
|
||||
Assert.Equal(1, EndCallbackReturn[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestOr1()
|
||||
{
|
||||
var cmd = BuildScript.Create("odasa", null, null, null) | BuildScript.Create("abc", "def ghi", null, null);
|
||||
|
||||
Actions.RunProcess["abc def ghi"] = 1;
|
||||
Actions.RunProcess["odasa "] = 0;
|
||||
cmd.Run(Actions, StartCallback, EndCallback);
|
||||
|
||||
Assert.Equal("odasa ", Actions.RunProcessIn[0]);
|
||||
Assert.Equal("odasa ", StartCallbackIn[0]);
|
||||
Assert.Equal("", EndCallbackIn[0]);
|
||||
Assert.Equal(0, EndCallbackReturn[0]);
|
||||
Assert.Equal(1, EndCallbackReturn.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestOr2()
|
||||
{
|
||||
var cmd = BuildScript.Create("abc", "def ghi", null, null) | BuildScript.Create("odasa", null, null, null);
|
||||
|
||||
Actions.RunProcess["abc def ghi"] = 1;
|
||||
Actions.RunProcess["odasa "] = 0;
|
||||
cmd.Run(Actions, StartCallback, EndCallback);
|
||||
|
||||
Assert.Equal("abc def ghi", Actions.RunProcessIn[0]);
|
||||
Assert.Equal("abc def ghi", StartCallbackIn[0]);
|
||||
Assert.Equal("", EndCallbackIn[0]);
|
||||
Assert.Equal(1, EndCallbackReturn[0]);
|
||||
|
||||
Assert.Equal("odasa ", Actions.RunProcessIn[1]);
|
||||
Assert.Equal("odasa ", StartCallbackIn[1]);
|
||||
Assert.Equal("", EndCallbackIn[1]);
|
||||
Assert.Equal(0, EndCallbackReturn[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestSuccess()
|
||||
{
|
||||
Assert.Equal(0, BuildScript.Success.Run(Actions, StartCallback, EndCallback));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestFailure()
|
||||
{
|
||||
Assert.NotEqual(0, BuildScript.Failure.Run(Actions, StartCallback, EndCallback));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDeleteDirectorySuccess()
|
||||
{
|
||||
Actions.DirectoryExists["trap"] = true;
|
||||
Assert.Equal(0, BuildScript.DeleteDirectory("trap").Run(Actions, StartCallback, EndCallback));
|
||||
Assert.Equal("trap", Actions.DirectoryDeleteIn[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDeleteDirectoryFailure()
|
||||
{
|
||||
Actions.DirectoryExists["trap"] = false;
|
||||
Assert.NotEqual(0, BuildScript.DeleteDirectory("trap").Run(Actions, StartCallback, EndCallback));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDeleteFileSuccess()
|
||||
{
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
Assert.Equal(0, BuildScript.DeleteFile("csharp.log").Run(Actions, StartCallback, EndCallback));
|
||||
Assert.Equal("csharp.log", Actions.FileExistsIn[0]);
|
||||
Assert.Equal("csharp.log", Actions.FileDeleteIn[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDeleteFileFailure()
|
||||
{
|
||||
Actions.FileExists["csharp.log"] = false;
|
||||
Assert.NotEqual(0, BuildScript.DeleteFile("csharp.log").Run(Actions, StartCallback, EndCallback));
|
||||
Assert.Equal("csharp.log", Actions.FileExistsIn[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestTry()
|
||||
{
|
||||
Assert.Equal(0, BuildScript.Try(BuildScript.Failure).Run(Actions, StartCallback, EndCallback));
|
||||
}
|
||||
|
||||
Autobuilder CreateAutoBuilder(string lgtmLanguage, bool isWindows,
|
||||
string buildless=null, string solution=null, string buildCommand=null, string ignoreErrors=null,
|
||||
string msBuildArguments=null, string msBuildPlatform=null, string msBuildConfiguration=null, string msBuildTarget=null,
|
||||
string dotnetArguments=null, string dotnetVersion=null, string vsToolsVersion=null,
|
||||
string nugetRestore=null, string allSolutions=null,
|
||||
string cwd=@"C:\Project")
|
||||
{
|
||||
Actions.GetEnvironmentVariable["SEMMLE_DIST"] = @"C:\odasa";
|
||||
Actions.GetEnvironmentVariable["SEMMLE_JAVA_HOME"] = @"C:\odasa\tools\java";
|
||||
Actions.GetEnvironmentVariable["LGTM_PROJECT_LANGUAGE"] = lgtmLanguage;
|
||||
Actions.GetEnvironmentVariable["SEMMLE_PLATFORM_TOOLS"] = @"C:\odasa\tools";
|
||||
Actions.GetEnvironmentVariable["LGTM_INDEX_VSTOOLS_VERSION"] = vsToolsVersion;
|
||||
Actions.GetEnvironmentVariable["LGTM_INDEX_MSBUILD_ARGUMENTS"] = msBuildArguments;
|
||||
Actions.GetEnvironmentVariable["LGTM_INDEX_MSBUILD_PLATFORM"] = msBuildPlatform;
|
||||
Actions.GetEnvironmentVariable["LGTM_INDEX_MSBUILD_CONFIGURATION"] = msBuildConfiguration;
|
||||
Actions.GetEnvironmentVariable["LGTM_INDEX_MSBUILD_TARGET"] = msBuildTarget;
|
||||
Actions.GetEnvironmentVariable["LGTM_INDEX_DOTNET_ARGUMENTS"] = dotnetArguments;
|
||||
Actions.GetEnvironmentVariable["LGTM_INDEX_DOTNET_VERSION"] = dotnetVersion;
|
||||
Actions.GetEnvironmentVariable["LGTM_INDEX_BUILD_COMMAND"] = buildCommand;
|
||||
Actions.GetEnvironmentVariable["LGTM_INDEX_SOLUTION"] = solution;
|
||||
Actions.GetEnvironmentVariable["LGTM_INDEX_IGNORE_ERRORS"] = ignoreErrors;
|
||||
Actions.GetEnvironmentVariable["LGTM_INDEX_BUILDLESS"] = buildless;
|
||||
Actions.GetEnvironmentVariable["LGTM_INDEX_ALL_SOLUTIONS"] = allSolutions;
|
||||
Actions.GetEnvironmentVariable["LGTM_INDEX_NUGET_RESTORE"] = nugetRestore;
|
||||
Actions.GetEnvironmentVariable["ProgramFiles(x86)"] = isWindows ? @"C:\Program Files (x86)" : null;
|
||||
Actions.GetCurrentDirectory = cwd;
|
||||
Actions.IsWindows = isWindows;
|
||||
|
||||
var options = new AutobuildOptions();
|
||||
options.ReadEnvironment(Actions);
|
||||
return new Autobuilder(Actions, options);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDefaultCSharpAutoBuilder()
|
||||
{
|
||||
Actions.RunProcess["cmd.exe /C dotnet --info"] = 0;
|
||||
Actions.RunProcess["cmd.exe /C dotnet clean"] = 0;
|
||||
Actions.RunProcess["cmd.exe /C dotnet restore"] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto dotnet build --no-incremental /p:UseSharedCompilation=false"] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbar.cs";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", true);
|
||||
TestAutobuilderScript(autobuilder, 0, 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestLinuxCSharpAutoBuilder()
|
||||
{
|
||||
Actions.RunProcess["dotnet --info"] = 0;
|
||||
Actions.RunProcess["dotnet clean"] = 0;
|
||||
Actions.RunProcess["dotnet restore"] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto dotnet build --no-incremental /p:UseSharedCompilation=false"] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", false);
|
||||
TestAutobuilderScript(autobuilder, 0, 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestLinuxCSharpAutoBuilderExtractorFailed()
|
||||
{
|
||||
Actions.RunProcess["dotnet --info"] = 0;
|
||||
Actions.RunProcess["dotnet clean"] = 0;
|
||||
Actions.RunProcess["dotnet restore"] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto dotnet build --no-incremental /p:UseSharedCompilation=false"] = 0;
|
||||
Actions.FileExists["csharp.log"] = false;
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", false);
|
||||
TestAutobuilderScript(autobuilder, 1, 4);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void TestDefaultCppAutobuilder()
|
||||
{
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("cpp", true);
|
||||
var script = autobuilder.GetBuildScript();
|
||||
|
||||
// Fails due to no solutions present.
|
||||
Assert.NotEqual(0, script.Run(Actions, StartCallback, EndCallback));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestCppAutobuilderSuccess()
|
||||
{
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\csharp\nuget\nuget.exe restore C:\Project\test.sln"] = 1;
|
||||
Actions.RunProcess[@"cmd.exe /C CALL ^""C:\Program Files ^(x86^)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat^"" && C:\odasa\tools\odasa index --auto msbuild C:\Project\test.sln /p:UseSharedCompilation=false /t:rebuild /p:Platform=""x86"" /p:Configuration=""Release"" /p:MvcBuildViews=true"] = 0;
|
||||
Actions.RunProcessOut[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationPath"] = "";
|
||||
Actions.RunProcess[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationPath"] = 1;
|
||||
Actions.RunProcess[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationVersion"] = 0;
|
||||
Actions.RunProcessOut[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationVersion"] = "";
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"] = true;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat"] = true;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat"] = true;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"] = true;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = true;
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.slx";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("cpp", true);
|
||||
var solution = new TestSolution(@"C:\Project\test.sln");
|
||||
autobuilder.SolutionsToBuild.Add(solution);
|
||||
TestAutobuilderScript(autobuilder, 0, 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestVsWhereSucceeded()
|
||||
{
|
||||
Actions.GetEnvironmentVariable["ProgramFiles(x86)"] = @"C:\Program Files (x86)";
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = true;
|
||||
Actions.RunProcess[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationPath"] = 0;
|
||||
Actions.RunProcessOut[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationPath"] = "C:\\VS1\nC:\\VS2";
|
||||
Actions.RunProcessOut[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationVersion"] = "10.0\n11.0";
|
||||
Actions.RunProcess[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationVersion"] = 0;
|
||||
|
||||
var candidates = BuildTools.GetCandidateVcVarsFiles(Actions).ToArray();
|
||||
Assert.Equal("C:\\VS1\\VC\\vcvarsall.bat", candidates[0].Path);
|
||||
Assert.Equal(10, candidates[0].ToolsVersion);
|
||||
Assert.Equal("C:\\VS2\\VC\\vcvarsall.bat", candidates[1].Path);
|
||||
Assert.Equal(11, candidates[1].ToolsVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestVsWhereNotExist()
|
||||
{
|
||||
Actions.GetEnvironmentVariable["ProgramFiles(x86)"] = @"C:\Program Files (x86)";
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = false;
|
||||
|
||||
var candidates = BuildTools.GetCandidateVcVarsFiles(Actions).ToArray();
|
||||
Assert.Equal(4, candidates.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestVcVarsAllBatFiles()
|
||||
{
|
||||
Actions.GetEnvironmentVariable["ProgramFiles(x86)"] = @"C:\Program Files (x86)";
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = false;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"] = true;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat"] = false;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat"] = true;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"] = false;
|
||||
|
||||
var vcvarsfiles = BuildTools.VcVarsAllBatFiles(Actions).ToArray();
|
||||
Assert.Equal(2, vcvarsfiles.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestLinuxBuildlessExtractionSuccess()
|
||||
{
|
||||
Actions.RunProcess[@"C:\odasa\tools\csharp\Semmle.Extraction.CSharp.Standalone --references:."] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", false, buildless:"true");
|
||||
TestAutobuilderScript(autobuilder, 0, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestLinuxBuildlessExtractionFailed()
|
||||
{
|
||||
Actions.RunProcess[@"C:\odasa\tools\csharp\Semmle.Extraction.CSharp.Standalone --references:."] = 10;
|
||||
Actions.RunProcess[@"C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", false, buildless: "true");
|
||||
TestAutobuilderScript(autobuilder, 10, 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestLinuxBuildlessExtractionSolution()
|
||||
{
|
||||
Actions.RunProcess[@"C:\odasa\tools\csharp\Semmle.Extraction.CSharp.Standalone foo.sln --references:."] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", false, buildless: "true", solution: "foo.sln");
|
||||
TestAutobuilderScript(autobuilder, 0, 3);
|
||||
}
|
||||
|
||||
void SkipVsWhere()
|
||||
{
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = false;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"] = false;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat"] = false;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat"] = false;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"] = false;
|
||||
}
|
||||
|
||||
void TestAutobuilderScript(Autobuilder autobuilder, int expectedOutput, int commandsRun)
|
||||
{
|
||||
Assert.Equal(expectedOutput, autobuilder.GetBuildScript().Run(Actions, StartCallback, EndCallback));
|
||||
|
||||
// Check expected commands actually ran
|
||||
Assert.Equal(commandsRun, StartCallbackIn.Count);
|
||||
Assert.Equal(commandsRun, EndCallbackIn.Count);
|
||||
Assert.Equal(commandsRun, EndCallbackReturn.Count);
|
||||
|
||||
var action = Actions.RunProcess.GetEnumerator();
|
||||
for(int cmd=0; cmd<commandsRun; ++cmd)
|
||||
{
|
||||
Assert.True(action.MoveNext());
|
||||
|
||||
Assert.Equal(action.Current.Key, StartCallbackIn[cmd]);
|
||||
Assert.Equal(action.Current.Value, EndCallbackReturn[cmd]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestLinuxBuildCommand()
|
||||
{
|
||||
Actions.RunProcess["C:\\odasa\\tools\\odasa index --auto \"./build.sh --skip-tests\""] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
SkipVsWhere();
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", false, buildCommand:"./build.sh --skip-tests");
|
||||
TestAutobuilderScript(autobuilder, 0, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestLinuxBuildSh()
|
||||
{
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbuild/build.sh";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.RunProcess["dotnet --info"] = 1;
|
||||
Actions.RunProcess["/bin/chmod u+x build/build.sh"] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto build/build.sh"] = 0;
|
||||
Actions.RunProcessWorkingDirectory[@"C:\odasa\tools\odasa index --auto build/build.sh"] = "build";
|
||||
Actions.RunProcess[@"C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", false);
|
||||
TestAutobuilderScript(autobuilder, 0, 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestLinuxBuildShCSharpLogMissing()
|
||||
{
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbuild.sh";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
|
||||
Actions.RunProcess["dotnet --info"] = 1;
|
||||
Actions.RunProcess["/bin/chmod u+x build.sh"] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto build.sh"] = 0;
|
||||
Actions.RunProcessWorkingDirectory[@"C:\odasa\tools\odasa index --auto build.sh"] = "";
|
||||
Actions.FileExists["csharp.log"] = false;
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", false);
|
||||
TestAutobuilderScript(autobuilder, 1, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestLinuxBuildShFailed()
|
||||
{
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbuild.sh";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
|
||||
Actions.RunProcess["dotnet --info"] = 1;
|
||||
Actions.RunProcess["/bin/chmod u+x build.sh"] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto build.sh"] = 5;
|
||||
Actions.RunProcessWorkingDirectory[@"C:\odasa\tools\odasa index --auto build.sh"] = "";
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", false);
|
||||
TestAutobuilderScript(autobuilder, 1, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestWindowsBuildBat()
|
||||
{
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbuild.bat";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.RunProcess["cmd.exe /C dotnet --info"] = 1;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto build.bat"] = 0;
|
||||
Actions.RunProcessWorkingDirectory[@"cmd.exe /C C:\odasa\tools\odasa index --auto build.bat"] = "";
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", true);
|
||||
TestAutobuilderScript(autobuilder, 0, 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestWindowsBuildBatIgnoreErrors()
|
||||
{
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbuild.bat";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.RunProcess["cmd.exe /C dotnet --info"] = 1;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto build.bat"] = 1;
|
||||
Actions.RunProcessWorkingDirectory[@"cmd.exe /C C:\odasa\tools\odasa index --auto build.bat"] = "";
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", true, ignoreErrors:"true");
|
||||
TestAutobuilderScript(autobuilder, 1, 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestWindowsCmdIgnoreErrors()
|
||||
{
|
||||
Actions.RunProcess["cmd.exe /C C:\\odasa\\tools\\odasa index --auto ^\"build.cmd --skip-tests^\""] = 3;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
SkipVsWhere();
|
||||
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", true, buildCommand: "build.cmd --skip-tests", ignoreErrors: "true");
|
||||
TestAutobuilderScript(autobuilder, 3, 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestWindowCSharpMsBuild()
|
||||
{
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\csharp\nuget\nuget.exe restore C:\Project\test1.sln"] = 0;
|
||||
Actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program Files ^(x86^)\\Microsoft Visual Studio 12.0\\VC\\vcvarsall.bat^\" && C:\\odasa\\tools\\odasa index --auto msbuild C:\\Project\\test1.sln /p:UseSharedCompilation=false /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /p:MvcBuildViews=true /P:Fu=Bar"] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\csharp\nuget\nuget.exe restore C:\Project\test2.sln"] = 0;
|
||||
Actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program Files ^(x86^)\\Microsoft Visual Studio 12.0\\VC\\vcvarsall.bat^\" && C:\\odasa\\tools\\odasa index --auto msbuild C:\\Project\\test2.sln /p:UseSharedCompilation=false /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /p:MvcBuildViews=true /P:Fu=Bar"] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = false;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"] = false;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat"] = true;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat"] = false;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"] = true;
|
||||
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest1.cs\ntest2.cs";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", true, msBuildArguments:"/P:Fu=Bar", msBuildTarget:"Windows", msBuildPlatform:"x86", msBuildConfiguration:"Debug",
|
||||
vsToolsVersion:"12", allSolutions:"true");
|
||||
var testSolution1 = new TestSolution(@"C:\Project\test1.sln");
|
||||
var testSolution2 = new TestSolution(@"C:\Project\test2.sln");
|
||||
autobuilder.SolutionsToBuild.Add(testSolution1);
|
||||
autobuilder.SolutionsToBuild.Add(testSolution2);
|
||||
|
||||
TestAutobuilderScript(autobuilder, 0, 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestWindowCSharpMsBuildFailed()
|
||||
{
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\csharp\nuget\nuget.exe restore C:\Project\test1.sln"] = 0;
|
||||
Actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program Files ^(x86^)\\Microsoft Visual Studio 12.0\\VC\\vcvarsall.bat^\" && C:\\odasa\\tools\\odasa index --auto msbuild C:\\Project\\test1.sln /p:UseSharedCompilation=false /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /p:MvcBuildViews=true /P:Fu=Bar"] = 1;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = false;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"] = false;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat"] = true;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat"] = false;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"] = true;
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest1.cs\ntest2.cs";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", true, msBuildArguments: "/P:Fu=Bar", msBuildTarget: "Windows", msBuildPlatform: "x86", msBuildConfiguration: "Debug",
|
||||
vsToolsVersion: "12", allSolutions: "true");
|
||||
var testSolution1 = new TestSolution(@"C:\Project\test1.sln");
|
||||
var testSolution2 = new TestSolution(@"C:\Project\test2.sln");
|
||||
autobuilder.SolutionsToBuild.Add(testSolution1);
|
||||
autobuilder.SolutionsToBuild.Add(testSolution2);
|
||||
|
||||
TestAutobuilderScript(autobuilder, 1, 2);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void TestSkipNugetMsBuild()
|
||||
{
|
||||
Actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program Files ^(x86^)\\Microsoft Visual Studio 12.0\\VC\\vcvarsall.bat^\" && C:\\odasa\\tools\\odasa index --auto msbuild C:\\Project\\test1.sln /p:UseSharedCompilation=false /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /p:MvcBuildViews=true /P:Fu=Bar"] = 0;
|
||||
Actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program Files ^(x86^)\\Microsoft Visual Studio 12.0\\VC\\vcvarsall.bat^\" && C:\\odasa\\tools\\odasa index --auto msbuild C:\\Project\\test2.sln /p:UseSharedCompilation=false /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /p:MvcBuildViews=true /P:Fu=Bar"] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = false;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"] = false;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat"] = true;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat"] = false;
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"] = true;
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest1.cs\ntest2.cs";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", true, msBuildArguments: "/P:Fu=Bar", msBuildTarget: "Windows",
|
||||
msBuildPlatform: "x86", msBuildConfiguration: "Debug", vsToolsVersion: "12",
|
||||
allSolutions: "true", nugetRestore:"false");
|
||||
var testSolution1 = new TestSolution(@"C:\Project\test1.sln");
|
||||
var testSolution2 = new TestSolution(@"C:\Project\test2.sln");
|
||||
autobuilder.SolutionsToBuild.Add(testSolution1);
|
||||
autobuilder.SolutionsToBuild.Add(testSolution2);
|
||||
|
||||
TestAutobuilderScript(autobuilder, 0, 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestSkipNugetBuildless()
|
||||
{
|
||||
Actions.RunProcess[@"C:\odasa\tools\csharp\Semmle.Extraction.CSharp.Standalone foo.sln --references:. --skip-nuget"] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", false, buildless: "true", solution: "foo.sln", nugetRestore:"false");
|
||||
TestAutobuilderScript(autobuilder, 0, 3);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void TestSkipNugetDotnet()
|
||||
{
|
||||
Actions.RunProcess["dotnet --info"] = 0;
|
||||
Actions.RunProcess["dotnet clean"] = 0;
|
||||
Actions.RunProcess["dotnet restore"] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto dotnet build --no-incremental /p:UseSharedCompilation=false --no-restore"] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", false, dotnetArguments:"--no-restore"); // nugetRestore=false does not work for now.
|
||||
TestAutobuilderScript(autobuilder, 0, 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDotnetVersionNotInstalled()
|
||||
{
|
||||
Actions.RunProcess["dotnet --list-sdks"] = 0;
|
||||
Actions.RunProcessOut["dotnet --list-sdks"] = "2.1.2 [C:\\Program Files\\dotnet\\sdks]\n2.1.4 [C:\\Program Files\\dotnet\\sdks]";
|
||||
Actions.RunProcess[@"curl -sO https://dot.net/v1/dotnet-install.sh"] = 0;
|
||||
Actions.RunProcess[@"chmod u+x dotnet-install.sh"] = 0;
|
||||
Actions.RunProcess[@"./dotnet-install.sh --channel release --version 2.1.3 --install-dir C:\Project\.dotnet"] = 0;
|
||||
Actions.RunProcess[@"C:\Project\.dotnet\dotnet --info"] = 0;
|
||||
Actions.RunProcess[@"C:\Project\.dotnet\dotnet clean"] = 0;
|
||||
Actions.RunProcess[@"C:\Project\.dotnet\dotnet restore"] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto C:\Project\.dotnet\dotnet build --no-incremental /p:UseSharedCompilation=false"] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.GetEnvironmentVariable["PATH"] = "/bin:/usr/bin";
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", false, dotnetVersion:"2.1.3");
|
||||
TestAutobuilderScript(autobuilder, 0, 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDotnetVersionAlreadyInstalled()
|
||||
{
|
||||
Actions.RunProcess["dotnet --list-sdks"] = 0;
|
||||
Actions.RunProcessOut["dotnet --list-sdks"] = "2.1.3 [C:\\Program Files\\dotnet\\sdks]\n2.1.4 [C:\\Program Files\\dotnet\\sdks]";
|
||||
Actions.RunProcess[@"curl -sO https://dot.net/v1/dotnet-install.sh"] = 0;
|
||||
Actions.RunProcess[@"chmod u+x dotnet-install.sh"] = 0;
|
||||
Actions.RunProcess[@"./dotnet-install.sh --channel release --version 2.1.3 --install-dir C:\Project\.dotnet"] = 0;
|
||||
Actions.RunProcess[@"C:\Project\.dotnet\dotnet --info"] = 0;
|
||||
Actions.RunProcess[@"C:\Project\.dotnet\dotnet clean"] = 0;
|
||||
Actions.RunProcess[@"C:\Project\.dotnet\dotnet restore"] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto C:\Project\.dotnet\dotnet build --no-incremental /p:UseSharedCompilation=false"] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.GetEnvironmentVariable["PATH"] = "/bin:/usr/bin";
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbar.cs";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", false, dotnetVersion: "2.1.3");
|
||||
TestAutobuilderScript(autobuilder, 0, 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDotnetVersionWindows()
|
||||
{
|
||||
Actions.RunProcess["cmd.exe /C dotnet --list-sdks"] = 0;
|
||||
Actions.RunProcessOut["cmd.exe /C dotnet --list-sdks"] = "2.1.3 [C:\\Program Files\\dotnet\\sdks]\n2.1.4 [C:\\Program Files\\dotnet\\sdks]";
|
||||
Actions.RunProcess[@"cmd.exe /C powershell -NoProfile -ExecutionPolicy unrestricted -file C:\Project\install-dotnet.ps1 -Version 2.1.3 -InstallDir C:\Project\.dotnet"] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet --info"] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet clean"] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet restore"] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto C:\Project\.dotnet\dotnet build --no-incremental /p:UseSharedCompilation=false"] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --xml --extensions config"] = 0;
|
||||
Actions.FileExists["csharp.log"] = true;
|
||||
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
|
||||
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
|
||||
Actions.GetEnvironmentVariable["PATH"] = "/bin:/usr/bin";
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
|
||||
var autobuilder = CreateAutoBuilder("csharp", true, dotnetVersion: "2.1.3");
|
||||
TestAutobuilderScript(autobuilder, 0, 8);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Semmle.Autobuild.Tests")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Semmle.Extraction.Tests")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2018")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -0,0 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit" Version="2.4.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Semmle.Autobuild\Semmle.Autobuild.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,20 @@
|
|||
using System.IO;
|
||||
|
||||
namespace Semmle.Autobuild
|
||||
{
|
||||
/// <summary>
|
||||
/// ASP extraction.
|
||||
/// </summary>
|
||||
class AspBuildRule : IBuildRule
|
||||
{
|
||||
public BuildScript Analyse(Autobuilder builder)
|
||||
{
|
||||
var command = new CommandBuilder(builder.Actions).
|
||||
RunCommand(builder.Actions.PathCombine(builder.SemmleJavaHome, "bin", "java")).
|
||||
Argument("-jar").
|
||||
QuoteArgument(builder.Actions.PathCombine(builder.SemmleDist, "tools", "extractor-asp.jar")).
|
||||
Argument(".");
|
||||
return command.Script;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Semmle.Autobuild
|
||||
{
|
||||
/// <summary>
|
||||
/// Encapsulates build options.
|
||||
/// </summary>
|
||||
public class AutobuildOptions
|
||||
{
|
||||
public readonly int SearchDepth = 3;
|
||||
public string RootDirectory = null;
|
||||
static readonly string prefix = "LGTM_INDEX_";
|
||||
|
||||
public string VsToolsVersion;
|
||||
public string MsBuildArguments;
|
||||
public string MsBuildPlatform;
|
||||
public string MsBuildConfiguration;
|
||||
public string MsBuildTarget;
|
||||
public string DotNetArguments;
|
||||
public string DotNetVersion;
|
||||
public string BuildCommand;
|
||||
public string[] Solution;
|
||||
|
||||
public bool IgnoreErrors;
|
||||
public bool Buildless;
|
||||
public bool AllSolutions;
|
||||
public bool NugetRestore;
|
||||
|
||||
public Language Language;
|
||||
|
||||
/// <summary>
|
||||
/// Reads options from environment variables.
|
||||
/// Throws ArgumentOutOfRangeException for invalid arguments.
|
||||
/// </summary>
|
||||
public void ReadEnvironment(IBuildActions actions)
|
||||
{
|
||||
RootDirectory = actions.GetCurrentDirectory();
|
||||
VsToolsVersion = actions.GetEnvironmentVariable(prefix + "VSTOOLS_VERSION");
|
||||
MsBuildArguments = actions.GetEnvironmentVariable(prefix + "MSBUILD_ARGUMENTS");
|
||||
MsBuildPlatform = actions.GetEnvironmentVariable(prefix + "MSBUILD_PLATFORM");
|
||||
MsBuildConfiguration = actions.GetEnvironmentVariable(prefix + "MSBUILD_CONFIGURATION");
|
||||
MsBuildTarget = actions.GetEnvironmentVariable(prefix + "MSBUILD_TARGET");
|
||||
DotNetArguments = actions.GetEnvironmentVariable(prefix + "DOTNET_ARGUMENTS");
|
||||
DotNetVersion = actions.GetEnvironmentVariable(prefix + "DOTNET_VERSION");
|
||||
BuildCommand = actions.GetEnvironmentVariable(prefix + "BUILD_COMMAND");
|
||||
Solution = actions.GetEnvironmentVariable(prefix + "SOLUTION").AsList(new string[0]);
|
||||
|
||||
IgnoreErrors = actions.GetEnvironmentVariable(prefix + "IGNORE_ERRORS").AsBool("ignore_errors", false);
|
||||
Buildless = actions.GetEnvironmentVariable(prefix + "BUILDLESS").AsBool("buildless", false);
|
||||
AllSolutions = actions.GetEnvironmentVariable(prefix + "ALL_SOLUTIONS").AsBool("all_solutions", false);
|
||||
NugetRestore = actions.GetEnvironmentVariable(prefix + "NUGET_RESTORE").AsBool("nuget_restore", true);
|
||||
|
||||
Language = actions.GetEnvironmentVariable("LGTM_PROJECT_LANGUAGE").AsLanguage();
|
||||
}
|
||||
}
|
||||
|
||||
public static class OptionsExtensions
|
||||
{
|
||||
public static bool AsBool(this string value, string param, bool defaultValue)
|
||||
{
|
||||
if (value == null) return defaultValue;
|
||||
switch (value.ToLower())
|
||||
{
|
||||
case "on":
|
||||
case "yes":
|
||||
case "true":
|
||||
case "enabled":
|
||||
return true;
|
||||
case "off":
|
||||
case "no":
|
||||
case "false":
|
||||
case "disabled":
|
||||
return false;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(param, value, "The Boolean value is invalid.");
|
||||
}
|
||||
}
|
||||
|
||||
public static Language AsLanguage(this string key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case null:
|
||||
throw new ArgumentException("Environment variable required: LGTM_PROJECT_LANGUAGE");
|
||||
case "csharp":
|
||||
return Language.CSharp;
|
||||
case "cpp":
|
||||
return Language.Cpp;
|
||||
default:
|
||||
throw new ArgumentException("Language key not understood: '" + key + "'");
|
||||
}
|
||||
}
|
||||
|
||||
public static string[] AsList(this string value, string[] defaultValue)
|
||||
{
|
||||
if (value == null)
|
||||
return defaultValue;
|
||||
|
||||
return value.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,352 @@
|
|||
using Semmle.Extraction.CSharp;
|
||||
using Semmle.Util.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Semmle.Autobuild
|
||||
{
|
||||
/// <summary>
|
||||
/// A build rule analyses the files in "builder" and outputs a build script.
|
||||
/// </summary>
|
||||
interface IBuildRule
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyse the files and produce a build script.
|
||||
/// </summary>
|
||||
/// <param name="builder">The files and options relating to the build.</param>
|
||||
BuildScript Analyse(Autobuilder builder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main application logic, containing all data
|
||||
/// gathered from the project and filesystem.
|
||||
///
|
||||
/// The overall design is intended to be extensible so that in theory,
|
||||
/// it should be possible to add new build rules without touching this code.
|
||||
/// </summary>
|
||||
public class Autobuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Full file paths of files found in the project directory.
|
||||
/// </summary>
|
||||
public IEnumerable<string> Paths => pathsLazy.Value;
|
||||
readonly Lazy<IEnumerable<string>> pathsLazy;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of paths matching a set of extensions
|
||||
/// (including the ".").
|
||||
/// </summary>
|
||||
/// <param name="extensions">The extensions to find.</param>
|
||||
/// <returns>The files matching the extension.</returns>
|
||||
public IEnumerable<string> GetExtensions(params string[] extensions) =>
|
||||
Paths.Where(p => extensions.Contains(Path.GetExtension(p)));
|
||||
|
||||
/// <summary>
|
||||
/// Gets all paths matching a particular filename.
|
||||
/// </summary>
|
||||
/// <param name="name">The filename to find.</param>
|
||||
/// <returns>Possibly empty sequence of paths with the given filename.</returns>
|
||||
public IEnumerable<string> GetFilename(string name) => Paths.Where(p => Path.GetFileName(p) == name);
|
||||
|
||||
/// <summary>
|
||||
/// Holds if a given path, relative to the root of the source directory
|
||||
/// was found.
|
||||
/// </summary>
|
||||
/// <param name="path">The relative path.</param>
|
||||
/// <returns>True iff the path was found.</returns>
|
||||
public bool HasRelativePath(string path) => HasPath(Actions.PathCombine(RootDirectory, path));
|
||||
|
||||
/// <summary>
|
||||
/// List of solution files to build.
|
||||
/// </summary>
|
||||
public IList<ISolution> SolutionsToBuild => solutionsToBuildLazy.Value;
|
||||
readonly Lazy<IList<ISolution>> solutionsToBuildLazy;
|
||||
|
||||
/// <summary>
|
||||
/// Holds if a given path was found.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file.</param>
|
||||
/// <returns>True iff the path was found.</returns>
|
||||
public bool HasPath(string path) => Paths.Any(p => path == p);
|
||||
|
||||
void FindFiles(string dir, int depth, IList<string> results)
|
||||
{
|
||||
foreach (var f in Actions.EnumerateFiles(dir))
|
||||
{
|
||||
results.Add(f);
|
||||
}
|
||||
|
||||
if (depth > 1)
|
||||
{
|
||||
foreach (var d in Actions.EnumerateDirectories(dir))
|
||||
{
|
||||
FindFiles(d, depth - 1, results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The root of the source directory.
|
||||
/// </summary>
|
||||
string RootDirectory => Options.RootDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the supplied build configuration.
|
||||
/// </summary>
|
||||
public AutobuildOptions Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The set of build actions used during the autobuilder.
|
||||
/// Could be real system operations, or a stub for testing.
|
||||
/// </summary>
|
||||
public IBuildActions Actions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Find all the relevant files and picks the best
|
||||
/// solution file and tools.
|
||||
/// </summary>
|
||||
/// <param name="options">The command line options.</param>
|
||||
public Autobuilder(IBuildActions actions, AutobuildOptions options)
|
||||
{
|
||||
Actions = actions;
|
||||
Options = options;
|
||||
|
||||
pathsLazy = new Lazy<IEnumerable<string>>(() =>
|
||||
{
|
||||
var files = new List<string>();
|
||||
FindFiles(options.RootDirectory, options.SearchDepth, files);
|
||||
return files.
|
||||
OrderBy(s => s.Count(c => c == Path.DirectorySeparatorChar)).
|
||||
ThenBy(s => Path.GetFileName(s).Length).
|
||||
ToArray();
|
||||
});
|
||||
|
||||
solutionsToBuildLazy = new Lazy<IList<ISolution>>(() =>
|
||||
{
|
||||
if (options.Solution.Any())
|
||||
{
|
||||
var ret = new List<ISolution>();
|
||||
foreach (var solution in options.Solution)
|
||||
{
|
||||
if (actions.FileExists(solution))
|
||||
ret.Add(new Solution(this, solution));
|
||||
else
|
||||
Log(Severity.Error, "The specified solution file {0} was not found", solution);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
var solutions = GetExtensions(".sln").
|
||||
Select(s => new Solution(this, s)).
|
||||
Where(s => s.ProjectCount > 0).
|
||||
OrderByDescending(s => s.ProjectCount).
|
||||
ThenBy(s => s.Path.Length).
|
||||
ToArray();
|
||||
|
||||
foreach (var sln in solutions)
|
||||
{
|
||||
Log(Severity.Info, $"Found {sln.Path} with {sln.ProjectCount} {this.Options.Language} projects, version {sln.ToolsVersion}, config {string.Join(" ", sln.Configurations.Select(c => c.FullName))}");
|
||||
}
|
||||
|
||||
return new List<ISolution>(options.AllSolutions ?
|
||||
solutions :
|
||||
solutions.Take(1));
|
||||
});
|
||||
|
||||
SemmleDist = Actions.GetEnvironmentVariable("SEMMLE_DIST");
|
||||
|
||||
SemmleJavaHome = Actions.GetEnvironmentVariable("SEMMLE_JAVA_HOME");
|
||||
|
||||
SemmlePlatformTools = Actions.GetEnvironmentVariable("SEMMLE_PLATFORM_TOOLS");
|
||||
|
||||
if (SemmleDist == null)
|
||||
Log(Severity.Error, "The environment variable SEMMLE_DIST has not been set.");
|
||||
}
|
||||
|
||||
readonly ILogger logger = new ConsoleLogger(Verbosity.Info);
|
||||
|
||||
/// <summary>
|
||||
/// Log a given build event to the console.
|
||||
/// </summary>
|
||||
/// <param name="format">The format string.</param>
|
||||
/// <param name="args">Inserts to the format string.</param>
|
||||
public void Log(Severity severity, string format, params object[] args)
|
||||
{
|
||||
logger.Log(severity, format, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to build this project.
|
||||
/// </summary>
|
||||
/// <returns>The exit code, 0 for success and non-zero for failures.</returns>
|
||||
public int AttemptBuild()
|
||||
{
|
||||
Log(Severity.Info, $"Working directory: {Options.RootDirectory}");
|
||||
|
||||
var script = GetBuildScript();
|
||||
if (Options.IgnoreErrors)
|
||||
script |= BuildScript.Success;
|
||||
|
||||
void startCallback(string s) => Log(Severity.Info, $"\nRunning {s}");
|
||||
void exitCallback(int ret, string msg) => Log(Severity.Info, $"Exit code {ret}{(string.IsNullOrEmpty(msg) ? "" : $": {msg}")}");
|
||||
return script.Run(Actions, startCallback, exitCallback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the build script to use for this project.
|
||||
/// </summary>
|
||||
public BuildScript GetBuildScript()
|
||||
{
|
||||
var isCSharp = Options.Language == Language.CSharp;
|
||||
return isCSharp ? GetCSharpBuildScript() : GetCppBuildScript();
|
||||
}
|
||||
|
||||
BuildScript GetCSharpBuildScript()
|
||||
{
|
||||
/// <summary>
|
||||
/// A script that checks that the C# extractor has been executed.
|
||||
/// </summary>
|
||||
BuildScript CheckExtractorRun(bool warnOnFailure) =>
|
||||
BuildScript.Create(actions =>
|
||||
{
|
||||
if (actions.FileExists(Extractor.GetCSharpLogPath()))
|
||||
return 0;
|
||||
|
||||
if (warnOnFailure)
|
||||
Log(Severity.Error, "No C# code detected during build.");
|
||||
|
||||
return 1;
|
||||
});
|
||||
|
||||
var attempt = BuildScript.Failure;
|
||||
switch (GetCSharpBuildStrategy())
|
||||
{
|
||||
case CSharpBuildStrategy.CustomBuildCommand:
|
||||
attempt = new BuildCommandRule().Analyse(this) & CheckExtractorRun(true);
|
||||
break;
|
||||
case CSharpBuildStrategy.Buildless:
|
||||
// No need to check that the extractor has been executed in buildless mode
|
||||
attempt = new StandaloneBuildRule().Analyse(this);
|
||||
break;
|
||||
case CSharpBuildStrategy.MSBuild:
|
||||
attempt = new MsBuildRule().Analyse(this) & CheckExtractorRun(true);
|
||||
break;
|
||||
case CSharpBuildStrategy.DotNet:
|
||||
attempt = new DotNetRule().Analyse(this) & CheckExtractorRun(true);
|
||||
break;
|
||||
case CSharpBuildStrategy.Auto:
|
||||
var cleanTrapFolder =
|
||||
BuildScript.DeleteDirectory(Actions.GetEnvironmentVariable("TRAP_FOLDER"));
|
||||
var cleanSourceArchive =
|
||||
BuildScript.DeleteDirectory(Actions.GetEnvironmentVariable("SOURCE_ARCHIVE"));
|
||||
var cleanExtractorLog =
|
||||
BuildScript.DeleteFile(Extractor.GetCSharpLogPath());
|
||||
var attemptExtractorCleanup =
|
||||
BuildScript.Try(cleanTrapFolder) &
|
||||
BuildScript.Try(cleanSourceArchive) &
|
||||
BuildScript.Try(cleanExtractorLog);
|
||||
|
||||
/// <summary>
|
||||
/// Execute script `s` and check that the C# extractor has been executed.
|
||||
/// If either fails, attempt to cleanup any artifacts produced by the extractor,
|
||||
/// and exit with code 1, in order to proceed to the next attempt.
|
||||
/// </summary>
|
||||
BuildScript IntermediateAttempt(BuildScript s) =>
|
||||
(s & CheckExtractorRun(false)) |
|
||||
(attemptExtractorCleanup & BuildScript.Failure);
|
||||
|
||||
attempt =
|
||||
// First try .NET Core
|
||||
IntermediateAttempt(new DotNetRule().Analyse(this)) |
|
||||
// Then MSBuild
|
||||
(() => IntermediateAttempt(new MsBuildRule().Analyse(this))) |
|
||||
// And finally look for a script that might be a build script
|
||||
(() => new BuildCommandAutoRule().Analyse(this) & CheckExtractorRun(true)) |
|
||||
// All attempts failed: print message
|
||||
AutobuildFailure();
|
||||
break;
|
||||
}
|
||||
|
||||
return
|
||||
attempt &
|
||||
(() => new AspBuildRule().Analyse(this)) &
|
||||
(() => new XmlBuildRule().Analyse(this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the build strategy that the autobuilder should apply, based on the
|
||||
/// options in the `lgtm.yml` file.
|
||||
/// </summary>
|
||||
CSharpBuildStrategy GetCSharpBuildStrategy()
|
||||
{
|
||||
if (Options.BuildCommand != null)
|
||||
return CSharpBuildStrategy.CustomBuildCommand;
|
||||
|
||||
if (Options.Buildless)
|
||||
return CSharpBuildStrategy.Buildless;
|
||||
|
||||
if (Options.MsBuildArguments != null
|
||||
|| Options.MsBuildConfiguration != null
|
||||
|| Options.MsBuildPlatform != null
|
||||
|| Options.MsBuildTarget != null)
|
||||
return CSharpBuildStrategy.MSBuild;
|
||||
|
||||
if (Options.DotNetArguments != null || Options.DotNetVersion != null)
|
||||
return CSharpBuildStrategy.DotNet;
|
||||
|
||||
return CSharpBuildStrategy.Auto;
|
||||
}
|
||||
|
||||
enum CSharpBuildStrategy
|
||||
{
|
||||
CustomBuildCommand,
|
||||
Buildless,
|
||||
MSBuild,
|
||||
DotNet,
|
||||
Auto
|
||||
}
|
||||
|
||||
BuildScript GetCppBuildScript()
|
||||
{
|
||||
if (Options.BuildCommand != null)
|
||||
return new BuildCommandRule().Analyse(this);
|
||||
|
||||
return
|
||||
// First try MSBuild
|
||||
new MsBuildRule().Analyse(this) |
|
||||
// Then look for a script that might be a build script
|
||||
(() => new BuildCommandAutoRule().Analyse(this)) |
|
||||
// All attempts failed: print message
|
||||
AutobuildFailure();
|
||||
}
|
||||
|
||||
BuildScript AutobuildFailure() =>
|
||||
BuildScript.Create(actions =>
|
||||
{
|
||||
Log(Severity.Error, "Could not auto-detect a suitable build method");
|
||||
return 1;
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Value of SEMMLE_DIST environment variable.
|
||||
/// </summary>
|
||||
public string SemmleDist { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Value of SEMMLE_JAVA_HOME environment variable.
|
||||
/// </summary>
|
||||
public string SemmleJavaHome { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Value of SEMMLE_PLATFORM_TOOLS environment variable.
|
||||
/// </summary>
|
||||
public string SemmlePlatformTools { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The absolute path of the odasa executable.
|
||||
/// </summary>
|
||||
public string Odasa => SemmleDist == null ? null : Actions.PathCombine(SemmleDist, "tools", "odasa");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
using Semmle.Util;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace Semmle.Autobuild
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper around system calls so that the build scripts can be unit-tested.
|
||||
/// </summary>
|
||||
public interface IBuildActions
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs a process and captures its output.
|
||||
/// </summary>
|
||||
/// <param name="exe">The exe to run.</param>
|
||||
/// <param name="args">The other command line arguments.</param>
|
||||
/// <param name="workingDirectory">The working directory (<code>null</code> for current directory).</param>
|
||||
/// <param name="env">Additional environment variables.</param>
|
||||
/// <param name="stdOut">The lines of stdout.</param>
|
||||
/// <returns>The process exit code.</returns>
|
||||
int RunProcess(string exe, string args, string workingDirectory, IDictionary<string, string> env, out IList<string> stdOut);
|
||||
|
||||
/// <summary>
|
||||
/// Runs a process but does not capture its output.
|
||||
/// </summary>
|
||||
/// <param name="exe">The exe to run.</param>
|
||||
/// <param name="args">The other command line arguments.</param>
|
||||
/// <param name="workingDirectory">The working directory (<code>null</code> for current directory).</param>
|
||||
/// <param name="env">Additional environment variables.</param>
|
||||
/// <returns>The process exit code.</returns>
|
||||
int RunProcess(string exe, string args, string workingDirectory, IDictionary<string, string> env);
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether a file exists, File.Exists().
|
||||
/// </summary>
|
||||
/// <param name="file">The filename.</param>
|
||||
/// <returns>True iff the file exists.</returns>
|
||||
bool FileExists(string file);
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether a directory exists, Directory.Exists().
|
||||
/// </summary>
|
||||
/// <param name="dir">The directory name.</param>
|
||||
/// <returns>True iff the directory exists.</returns>
|
||||
bool DirectoryExists(string dir);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a file, File.Delete().
|
||||
/// </summary>
|
||||
/// <param name="file">The filename.</param>
|
||||
void FileDelete(string file);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a directory, Directory.Delete().
|
||||
/// </summary>
|
||||
void DirectoryDelete(string dir, bool recursive);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an environment variable, Environment.GetEnvironmentVariable().
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the variable.</param>
|
||||
/// <returns>The string value, or null if the variable is not defined.</returns>
|
||||
string GetEnvironmentVariable(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current directory, Directory.GetCurrentDirectory().
|
||||
/// </summary>
|
||||
/// <returns>The current directory.</returns>
|
||||
string GetCurrentDirectory();
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates files in a directory, Directory.EnumerateFiles().
|
||||
/// </summary>
|
||||
/// <param name="dir">The directory to enumerate.</param>
|
||||
/// <returns>A list of filenames, or an empty list.</returns>
|
||||
IEnumerable<string> EnumerateFiles(string dir);
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the directories in a directory, Directory.EnumerateDirectories().
|
||||
/// </summary>
|
||||
/// <param name="dir">The directory to enumerate.</param>
|
||||
/// <returns>List of subdirectories, or empty list.</returns>
|
||||
IEnumerable<string> EnumerateDirectories(string dir);
|
||||
|
||||
/// <summary>
|
||||
/// True if we are running on Windows.
|
||||
/// </summary>
|
||||
bool IsWindows();
|
||||
|
||||
/// <summary>
|
||||
/// Combine path segments, Path.Combine().
|
||||
/// </summary>
|
||||
/// <param name="parts">The parts of the path.</param>
|
||||
/// <returns>The combined path.</returns>
|
||||
string PathCombine(params string[] parts);
|
||||
|
||||
/// <summary>
|
||||
/// Writes contents to file, File.WriteAllText().
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="contents">The text.</param>
|
||||
void WriteAllText(string filename, string contents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An implementation of IBuildActions that actually performs the requested operations.
|
||||
/// </summary>
|
||||
class SystemBuildActions : IBuildActions
|
||||
{
|
||||
void IBuildActions.FileDelete(string file) => File.Delete(file);
|
||||
|
||||
bool IBuildActions.FileExists(string file) => File.Exists(file);
|
||||
|
||||
ProcessStartInfo GetProcessStartInfo(string exe, string arguments, string workingDirectory, IDictionary<string, string> environment, bool redirectStandardOutput)
|
||||
{
|
||||
var pi = new ProcessStartInfo(exe, arguments)
|
||||
{
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = redirectStandardOutput
|
||||
};
|
||||
if (workingDirectory != null)
|
||||
pi.WorkingDirectory = workingDirectory;
|
||||
|
||||
// Environment variables can only be used when not redirecting stdout
|
||||
if (!redirectStandardOutput)
|
||||
{
|
||||
pi.Environment["UseSharedCompilation"] = "false";
|
||||
if (environment != null)
|
||||
environment.ForEach(kvp => pi.Environment[kvp.Key] = kvp.Value);
|
||||
}
|
||||
return pi;
|
||||
}
|
||||
|
||||
int IBuildActions.RunProcess(string cmd, string args, string workingDirectory, IDictionary<string, string> environment)
|
||||
{
|
||||
var pi = GetProcessStartInfo(cmd, args, workingDirectory, environment, false);
|
||||
using (var p = Process.Start(pi))
|
||||
{
|
||||
p.WaitForExit();
|
||||
return p.ExitCode;
|
||||
}
|
||||
}
|
||||
|
||||
int IBuildActions.RunProcess(string cmd, string args, string workingDirectory, IDictionary<string, string> environment, out IList<string> stdOut)
|
||||
{
|
||||
var pi = GetProcessStartInfo(cmd, args, workingDirectory, environment, true);
|
||||
return pi.ReadOutput(out stdOut);
|
||||
}
|
||||
|
||||
void IBuildActions.DirectoryDelete(string dir, bool recursive) => Directory.Delete(dir, recursive);
|
||||
|
||||
bool IBuildActions.DirectoryExists(string dir) => Directory.Exists(dir);
|
||||
|
||||
string IBuildActions.GetEnvironmentVariable(string name) => Environment.GetEnvironmentVariable(name);
|
||||
|
||||
string IBuildActions.GetCurrentDirectory() => Directory.GetCurrentDirectory();
|
||||
|
||||
IEnumerable<string> IBuildActions.EnumerateFiles(string dir) => Directory.EnumerateFiles(dir);
|
||||
|
||||
IEnumerable<string> IBuildActions.EnumerateDirectories(string dir) => Directory.EnumerateDirectories(dir);
|
||||
|
||||
bool IBuildActions.IsWindows() => Win32.IsWindows();
|
||||
|
||||
string IBuildActions.PathCombine(params string[] parts) => Path.Combine(parts);
|
||||
|
||||
void IBuildActions.WriteAllText(string filename, string contents) => File.WriteAllText(filename, contents);
|
||||
|
||||
private SystemBuildActions()
|
||||
{
|
||||
}
|
||||
|
||||
public static readonly IBuildActions Instance = new SystemBuildActions();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Semmle.Util;
|
||||
using Semmle.Util.Logging;
|
||||
|
||||
namespace Semmle.Autobuild
|
||||
{
|
||||
/// <summary>
|
||||
/// Auto-detection of build scripts.
|
||||
/// </summary>
|
||||
class BuildCommandAutoRule : IBuildRule
|
||||
{
|
||||
readonly IEnumerable<string> winExtensions = new List<string> {
|
||||
".bat",
|
||||
".cmd",
|
||||
".exe"
|
||||
};
|
||||
|
||||
readonly IEnumerable<string> linuxExtensions = new List<string> {
|
||||
"",
|
||||
".sh"
|
||||
};
|
||||
|
||||
readonly IEnumerable<string> buildScripts = new List<string> {
|
||||
"build"
|
||||
};
|
||||
|
||||
public BuildScript Analyse(Autobuilder builder)
|
||||
{
|
||||
builder.Log(Severity.Info, "Attempting to locate build script");
|
||||
|
||||
var extensions = builder.Actions.IsWindows() ? winExtensions : linuxExtensions;
|
||||
var scripts = buildScripts.SelectMany(s => extensions.Select(e => s + e));
|
||||
var scriptPath = builder.Paths.Where(p => scripts.Any(p.ToLower().EndsWith)).OrderBy(p => p.Length).FirstOrDefault();
|
||||
|
||||
if (scriptPath == null)
|
||||
return BuildScript.Failure;
|
||||
|
||||
var chmod = new CommandBuilder(builder.Actions);
|
||||
chmod.RunCommand("/bin/chmod", $"u+x {scriptPath}");
|
||||
var chmodScript = builder.Actions.IsWindows() ? BuildScript.Success : BuildScript.Try(chmod.Script);
|
||||
|
||||
var path = Path.GetDirectoryName(scriptPath);
|
||||
|
||||
// A specific .NET Core version may be required
|
||||
return chmodScript & DotNetRule.WithDotNet(builder, dotNet =>
|
||||
{
|
||||
var command = new CommandBuilder(builder.Actions, path, dotNet?.Environment);
|
||||
|
||||
// A specific Visual Studio version may be required
|
||||
var vsTools = MsBuildRule.GetVcVarsBatFile(builder);
|
||||
if (vsTools != null)
|
||||
command.CallBatFile(vsTools.Path);
|
||||
|
||||
command.IndexCommand(builder.Odasa, scriptPath);
|
||||
return command.Script;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
namespace Semmle.Autobuild
|
||||
{
|
||||
/// <summary>
|
||||
/// Execute the build_command rule.
|
||||
/// </summary>
|
||||
class BuildCommandRule : IBuildRule
|
||||
{
|
||||
public BuildScript Analyse(Autobuilder builder)
|
||||
{
|
||||
if (builder.Options.BuildCommand == null)
|
||||
return BuildScript.Failure;
|
||||
|
||||
// Custom build commands may require a specific .NET Core version
|
||||
return DotNetRule.WithDotNet(builder, dotNet =>
|
||||
{
|
||||
var command = new CommandBuilder(builder.Actions, null, dotNet?.Environment);
|
||||
|
||||
// Custom build commands may require a specific Visual Studio version
|
||||
var vsTools = MsBuildRule.GetVcVarsBatFile(builder);
|
||||
if (vsTools != null)
|
||||
command.CallBatFile(vsTools.Path);
|
||||
command.IndexCommand(builder.Odasa, builder.Options.BuildCommand);
|
||||
|
||||
return command.Script;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,274 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Semmle.Util;
|
||||
|
||||
namespace Semmle.Autobuild
|
||||
{
|
||||
/// <summary>
|
||||
/// A build script.
|
||||
/// </summary>
|
||||
public abstract class BuildScript
|
||||
{
|
||||
/// <summary>
|
||||
/// Run this build script.
|
||||
/// </summary>
|
||||
/// <param name="actions">
|
||||
/// The interface used to implement the build actions.
|
||||
/// </param>
|
||||
/// <param name="startCallback">
|
||||
/// A call back that is called every time a new process is started. The
|
||||
/// argument to the call back is a textual representation of the process.
|
||||
/// </param>
|
||||
/// <param name="exitCallBack">
|
||||
/// A call back that is called every time a new process exits. The first
|
||||
/// argument to the call back is the exit code, and the second argument is
|
||||
/// an exit message.
|
||||
/// </param>
|
||||
/// <returns>The exit code from this build script.</returns>
|
||||
public abstract int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack);
|
||||
|
||||
/// <summary>
|
||||
/// Run this build command.
|
||||
/// </summary>
|
||||
/// <param name="actions">
|
||||
/// The interface used to implement the build actions.
|
||||
/// </param>
|
||||
/// <param name="startCallback">
|
||||
/// A call back that is called every time a new process is started. The
|
||||
/// argument to the call back is a textual representation of the process.
|
||||
/// </param>
|
||||
/// <param name="exitCallBack">
|
||||
/// A call back that is called every time a new process exits. The first
|
||||
/// argument to the call back is the exit code, and the second argument is
|
||||
/// an exit message.
|
||||
/// </param>
|
||||
/// <param name="stdout">Contents of standard out.</param>
|
||||
/// <returns>The exit code from this build script.</returns>
|
||||
public abstract int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack, out IList<string> stdout);
|
||||
|
||||
class BuildCommand : BuildScript
|
||||
{
|
||||
readonly string exe, arguments, workingDirectory;
|
||||
readonly IDictionary<string, string> environment;
|
||||
|
||||
/// <summary>
|
||||
/// Create a simple build command.
|
||||
/// </summary>
|
||||
/// <param name="exe">The executable to run.</param>
|
||||
/// <param name="argumentsOpt">The arguments to the executable, or null.</param>
|
||||
/// <param name="workingDirectory">The working directory (<code>null</code> for current directory).</param>
|
||||
/// <param name="environment">Additional environment variables.</param>
|
||||
public BuildCommand(string exe, string argumentsOpt, string workingDirectory = null, IDictionary<string, string> environment = null)
|
||||
{
|
||||
this.exe = exe;
|
||||
this.arguments = argumentsOpt ?? "";
|
||||
this.workingDirectory = workingDirectory;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
public override string ToString() => exe + " " + arguments;
|
||||
|
||||
public override int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack)
|
||||
{
|
||||
startCallback(this.ToString());
|
||||
var ret = 1;
|
||||
var retMessage = "";
|
||||
try
|
||||
{
|
||||
ret = actions.RunProcess(exe, arguments, workingDirectory, environment);
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (ex is System.ComponentModel.Win32Exception || ex is FileNotFoundException)
|
||||
{
|
||||
retMessage = ex.Message;
|
||||
}
|
||||
|
||||
exitCallBack(ret, retMessage);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public override int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack, out IList<string> stdout)
|
||||
{
|
||||
startCallback(this.ToString());
|
||||
var ret = 1;
|
||||
var retMessage = "";
|
||||
try
|
||||
{
|
||||
ret = actions.RunProcess(exe, arguments, workingDirectory, environment, out stdout);
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (ex is System.ComponentModel.Win32Exception || ex is FileNotFoundException)
|
||||
{
|
||||
retMessage = ex.Message;
|
||||
stdout = new string[0];
|
||||
}
|
||||
exitCallBack(ret, retMessage);
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ReturnBuildCommand : BuildScript
|
||||
{
|
||||
readonly Func<IBuildActions, int> func;
|
||||
public ReturnBuildCommand(Func<IBuildActions, int> func)
|
||||
{
|
||||
this.func = func;
|
||||
}
|
||||
|
||||
public override int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack) => func(actions);
|
||||
|
||||
public override int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack, out IList<string> stdout)
|
||||
{
|
||||
stdout = new string[0];
|
||||
return func(actions);
|
||||
}
|
||||
}
|
||||
|
||||
class BindBuildScript : BuildScript
|
||||
{
|
||||
readonly BuildScript s1;
|
||||
readonly Func<IList<string>, int, BuildScript> s2a;
|
||||
readonly Func<int, BuildScript> s2b;
|
||||
public BindBuildScript(BuildScript s1, Func<IList<string>, int, BuildScript> s2)
|
||||
{
|
||||
this.s1 = s1;
|
||||
this.s2a = s2;
|
||||
}
|
||||
|
||||
public BindBuildScript(BuildScript s1, Func<int, BuildScript> s2)
|
||||
{
|
||||
this.s1 = s1;
|
||||
this.s2b = s2;
|
||||
}
|
||||
|
||||
public override int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack)
|
||||
{
|
||||
int ret1;
|
||||
if (s2a != null)
|
||||
{
|
||||
ret1 = s1.Run(actions, startCallback, exitCallBack, out var stdout1);
|
||||
return s2a(stdout1, ret1).Run(actions, startCallback, exitCallBack);
|
||||
}
|
||||
|
||||
ret1 = s1.Run(actions, startCallback, exitCallBack);
|
||||
return s2b(ret1).Run(actions, startCallback, exitCallBack);
|
||||
}
|
||||
|
||||
public override int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack, out IList<string> stdout)
|
||||
{
|
||||
var ret1 = s1.Run(actions, startCallback, exitCallBack, out var stdout1);
|
||||
var ret2 = (s2a != null ? s2a(stdout1, ret1) : s2b(ret1)).Run(actions, startCallback, exitCallBack, out var stdout2);
|
||||
var @out = new List<string>();
|
||||
@out.AddRange(stdout1);
|
||||
@out.AddRange(stdout2);
|
||||
stdout = @out;
|
||||
return ret2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a simple build script that runs the specified exe.
|
||||
/// </summary>
|
||||
/// <param name="argumentsOpt">The arguments to the executable, or null.</param>
|
||||
/// <param name="workingDirectory">The working directory (<code>null</code> for current directory).</param>
|
||||
/// <param name="environment">Additional environment variables.</param>
|
||||
public static BuildScript Create(string exe, string argumentsOpt, string workingDirectory, IDictionary<string, string> environment) =>
|
||||
new BuildCommand(exe, argumentsOpt, workingDirectory, environment);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a simple build script that runs the specified function.
|
||||
/// </summary>
|
||||
public static BuildScript Create(Func<IBuildActions, int> func) =>
|
||||
new ReturnBuildCommand(func);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a build script that runs <paramref name="s1"/>, followed by running the script
|
||||
/// produced by <paramref name="s2"/> on the exit code from <paramref name="s1"/>.
|
||||
/// </summary>
|
||||
public static BuildScript Bind(BuildScript s1, Func<int, BuildScript> s2) =>
|
||||
new BindBuildScript(s1, s2);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a build script that runs <paramref name="s1"/>, followed by running the script
|
||||
/// produced by <paramref name="s2"/> on the exit code and standard output from
|
||||
/// <paramref name="s1"/>.
|
||||
/// </summary>
|
||||
public static BuildScript Bind(BuildScript s1, Func<IList<string>, int, BuildScript> s2) =>
|
||||
new BindBuildScript(s1, s2);
|
||||
|
||||
const int SuccessCode = 0;
|
||||
/// <summary>
|
||||
/// The empty build script that always returns exit code 0.
|
||||
/// </summary>
|
||||
public static readonly BuildScript Success = Create(actions => SuccessCode);
|
||||
|
||||
const int FailureCode = 1;
|
||||
/// <summary>
|
||||
/// The empty build script that always returns exit code 1.
|
||||
/// </summary>
|
||||
public static readonly BuildScript Failure = Create(actions => FailureCode);
|
||||
|
||||
static bool Succeeded(int i) => i == SuccessCode;
|
||||
|
||||
public static BuildScript operator &(BuildScript s1, BuildScript s2) =>
|
||||
new BindBuildScript(s1, ret1 => Succeeded(ret1) ? s2 : Create(actions => ret1));
|
||||
|
||||
public static BuildScript operator &(BuildScript s1, Func<BuildScript> s2) =>
|
||||
new BindBuildScript(s1, ret1 => Succeeded(ret1) ? s2() : Create(actions => ret1));
|
||||
|
||||
public static BuildScript operator |(BuildScript s1, BuildScript s2) =>
|
||||
new BindBuildScript(s1, ret1 => Succeeded(ret1) ? Success : s2);
|
||||
|
||||
public static BuildScript operator |(BuildScript s1, Func<BuildScript> s2) =>
|
||||
new BindBuildScript(s1, ret1 => Succeeded(ret1) ? Success : s2());
|
||||
|
||||
/// <summary>
|
||||
/// Creates a build script that attempts to run the build script <paramref name="s"/>,
|
||||
/// always returning a successful exit code.
|
||||
/// </summary>
|
||||
public static BuildScript Try(BuildScript s) => s | Success;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a build script that deletes the given directory.
|
||||
/// </summary>
|
||||
public static BuildScript DeleteDirectory(string dir) =>
|
||||
Create(actions =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(dir) || !actions.DirectoryExists(dir))
|
||||
return FailureCode;
|
||||
|
||||
try
|
||||
{
|
||||
actions.DirectoryDelete(dir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return FailureCode;
|
||||
}
|
||||
return SuccessCode;
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Creates a build script that deletes the given file.
|
||||
/// </summary>
|
||||
public static BuildScript DeleteFile(string file) =>
|
||||
Create(actions =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(file) || !actions.FileExists(file))
|
||||
return FailureCode;
|
||||
|
||||
try
|
||||
{
|
||||
actions.FileDelete(file);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return FailureCode;
|
||||
}
|
||||
return SuccessCode;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Semmle.Autobuild
|
||||
{
|
||||
/// <summary>
|
||||
/// A BAT file used to initialise the appropriate
|
||||
/// Visual Studio version/platform.
|
||||
/// </summary>
|
||||
public class VcVarsBatFile
|
||||
{
|
||||
public readonly int ToolsVersion;
|
||||
public readonly string Path;
|
||||
public readonly string[] Platform;
|
||||
|
||||
public VcVarsBatFile(string path, int version, params string[] platform)
|
||||
{
|
||||
Path = path;
|
||||
ToolsVersion = version;
|
||||
Platform = platform;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Collection of available Visual Studio build tools.
|
||||
/// </summary>
|
||||
public static class BuildTools
|
||||
{
|
||||
public static IEnumerable<VcVarsBatFile> GetCandidateVcVarsFiles(IBuildActions actions)
|
||||
{
|
||||
var programFilesx86 = actions.GetEnvironmentVariable("ProgramFiles(x86)");
|
||||
if (programFilesx86 == null)
|
||||
yield break;
|
||||
|
||||
// Attempt to use vswhere to find installations of Visual Studio
|
||||
string vswhere = actions.PathCombine(programFilesx86, "Microsoft Visual Studio", "Installer", "vswhere.exe");
|
||||
|
||||
if (actions.FileExists(vswhere))
|
||||
{
|
||||
int exitCode1 = actions.RunProcess(vswhere, "-prerelease -legacy -property installationPath", null, null, out var installationList);
|
||||
int exitCode2 = actions.RunProcess(vswhere, "-prerelease -legacy -property installationVersion", null, null, out var versionList);
|
||||
|
||||
if (exitCode1 == 0 && exitCode2 == 0 && versionList.Count == installationList.Count)
|
||||
{
|
||||
// vswhere ran successfully and produced the expected output
|
||||
foreach (var vsInstallation in versionList.Zip(installationList, (v, i) => (Version: v, InstallationPath: i)))
|
||||
{
|
||||
var dot = vsInstallation.Version.IndexOf('.');
|
||||
var majorVersionString = dot == -1 ? vsInstallation.Version : vsInstallation.Version.Substring(0, dot);
|
||||
if (int.TryParse(majorVersionString, out int majorVersion))
|
||||
{
|
||||
if (majorVersion < 15)
|
||||
{
|
||||
yield return new VcVarsBatFile(actions.PathCombine(vsInstallation.InstallationPath, @"VC\vcvarsall.bat"), majorVersion, "x86");
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new VcVarsBatFile(actions.PathCombine(vsInstallation.InstallationPath, @"VC\Auxiliary\Build\vcvars32.bat"), majorVersion, "x86");
|
||||
yield return new VcVarsBatFile(actions.PathCombine(vsInstallation.InstallationPath, @"VC\Auxiliary\Build\vcvars64.bat"), majorVersion, "x64");
|
||||
}
|
||||
}
|
||||
// else: Skip installation without a version
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
// vswhere not installed or didn't run correctly - return legacy Visual Studio versions
|
||||
yield return new VcVarsBatFile(actions.PathCombine(programFilesx86, @"Microsoft Visual Studio 14.0\VC\vcvarsall.bat"), 14, "x86");
|
||||
yield return new VcVarsBatFile(actions.PathCombine(programFilesx86, @"Microsoft Visual Studio 12.0\VC\vcvarsall.bat"), 12, "x86");
|
||||
yield return new VcVarsBatFile(actions.PathCombine(programFilesx86, @"Microsoft Visual Studio 11.0\VC\vcvarsall.bat"), 11, "x86");
|
||||
yield return new VcVarsBatFile(actions.PathCombine(programFilesx86, @"Microsoft Visual Studio 10.0\VC\vcvarsall.bat"), 10, "x86");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates all available tools.
|
||||
/// </summary>
|
||||
public static IEnumerable<VcVarsBatFile> VcVarsAllBatFiles(IBuildActions actions) =>
|
||||
GetCandidateVcVarsFiles(actions).Where(v => actions.FileExists(v.Path));
|
||||
|
||||
/// <summary>
|
||||
/// Finds a VcVars file that provides a compatible environment for the given solution.
|
||||
/// </summary>
|
||||
/// <param name="sln">The solution file.</param>
|
||||
/// <returns>A compatible file, or throws an exception.</returns>
|
||||
public static VcVarsBatFile FindCompatibleVcVars(IBuildActions actions, ISolution sln) =>
|
||||
FindCompatibleVcVars(actions, sln.ToolsVersion.Major);
|
||||
|
||||
/// <summary>
|
||||
/// Finds a VcVars that provides a compatible environment for the given tools version.
|
||||
/// </summary>
|
||||
/// <param name="targetVersion">The tools version.</param>
|
||||
/// <returns>A compatible file, or null.</returns>
|
||||
public static VcVarsBatFile FindCompatibleVcVars(IBuildActions actions, int targetVersion) =>
|
||||
targetVersion < 10 ?
|
||||
VcVarsAllBatFiles(actions).OrderByDescending(b => b.ToolsVersion).FirstOrDefault() :
|
||||
VcVarsAllBatFiles(actions).Where(b => b.ToolsVersion >= targetVersion).OrderBy(b => b.ToolsVersion).FirstOrDefault();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Semmle.Autobuild
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility to construct a build command.
|
||||
/// </summary>
|
||||
class CommandBuilder
|
||||
{
|
||||
enum EscapeMode { Process, Cmd };
|
||||
|
||||
readonly StringBuilder arguments;
|
||||
bool firstCommand;
|
||||
string executable;
|
||||
readonly EscapeMode escapingMode;
|
||||
readonly string workingDirectory;
|
||||
readonly IDictionary<string, string> environment;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:Semmle.Autobuild.CommandBuilder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="workingDirectory">The working directory (<code>null</code> for current directory).</param>
|
||||
/// <param name="environment">Additional environment variables.</param>
|
||||
public CommandBuilder(IBuildActions actions, string workingDirectory = null, IDictionary<string, string> environment = null)
|
||||
{
|
||||
arguments = new StringBuilder();
|
||||
if (actions.IsWindows())
|
||||
{
|
||||
executable = "cmd.exe";
|
||||
arguments.Append("/C");
|
||||
escapingMode = EscapeMode.Cmd;
|
||||
}
|
||||
else
|
||||
{
|
||||
escapingMode = EscapeMode.Process;
|
||||
}
|
||||
|
||||
firstCommand = true;
|
||||
this.workingDirectory = workingDirectory;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
void OdasaIndex(string odasa)
|
||||
{
|
||||
RunCommand(odasa, "index --auto");
|
||||
}
|
||||
|
||||
public CommandBuilder CallBatFile(string batFile, string argumentsOpt = null)
|
||||
{
|
||||
NextCommand();
|
||||
arguments.Append(" CALL");
|
||||
QuoteArgument(batFile);
|
||||
Argument(argumentsOpt);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform odasa index on a given command or BAT file.
|
||||
/// </summary>
|
||||
/// <param name="odasa">The odasa executable.</param>
|
||||
/// <param name="command">The command to run.</param>
|
||||
/// <param name="argumentsOpt">Additional arguments.</param>
|
||||
/// <returns>this for chaining calls.</returns>
|
||||
public CommandBuilder IndexCommand(string odasa, string command, string argumentsOpt = null)
|
||||
{
|
||||
OdasaIndex(odasa);
|
||||
QuoteArgument(command);
|
||||
Argument(argumentsOpt);
|
||||
return this;
|
||||
}
|
||||
|
||||
static readonly char[] specialChars = { ' ', '\t', '\n', '\v', '\"' };
|
||||
static readonly char[] cmdMetacharacter = { '(', ')', '%', '!', '^', '\"', '<', '>', '&', '|' };
|
||||
|
||||
/// <summary>
|
||||
/// Appends the given argument to the command line.
|
||||
/// </summary>
|
||||
/// <param name="argument">The argument to append.</param>
|
||||
/// <param name="force">Whether to always quote the argument.</param>
|
||||
/// <param name="cmd">Whether to escape for cmd.exe</param>
|
||||
///
|
||||
/// <remarks>
|
||||
/// This implementation is copied from
|
||||
/// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
|
||||
/// </remarks>
|
||||
void ArgvQuote(string argument, bool force)
|
||||
{
|
||||
bool cmd = escapingMode == EscapeMode.Cmd;
|
||||
if (!force &&
|
||||
!string.IsNullOrEmpty(argument) &&
|
||||
argument.IndexOfAny(specialChars) == -1)
|
||||
{
|
||||
arguments.Append(argument);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cmd) arguments.Append('^');
|
||||
arguments.Append('\"');
|
||||
for (int it = 0; ; ++it)
|
||||
{
|
||||
var numBackslashes = 0;
|
||||
while (it != argument.Length && argument[it] == '\\')
|
||||
{
|
||||
++it;
|
||||
++numBackslashes;
|
||||
}
|
||||
|
||||
if (it == argument.Length)
|
||||
{
|
||||
arguments.Append('\\', numBackslashes * 2);
|
||||
break;
|
||||
}
|
||||
else if (argument[it] == '\"')
|
||||
{
|
||||
arguments.Append('\\', numBackslashes * 2 + 1);
|
||||
if (cmd) arguments.Append('^');
|
||||
arguments.Append(arguments[it]);
|
||||
}
|
||||
else
|
||||
{
|
||||
arguments.Append('\\', numBackslashes);
|
||||
if (cmd && cmdMetacharacter.Any(c => c == argument[it]))
|
||||
arguments.Append('^');
|
||||
|
||||
arguments.Append(argument[it]);
|
||||
}
|
||||
}
|
||||
if (cmd) arguments.Append('^');
|
||||
arguments.Append('\"');
|
||||
}
|
||||
}
|
||||
|
||||
public CommandBuilder QuoteArgument(string argumentsOpt)
|
||||
{
|
||||
if (argumentsOpt != null)
|
||||
{
|
||||
NextArgument();
|
||||
ArgvQuote(argumentsOpt, false);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
void NextArgument()
|
||||
{
|
||||
if (arguments.Length > 0)
|
||||
arguments.Append(' ');
|
||||
}
|
||||
|
||||
public CommandBuilder Argument(string argumentsOpt)
|
||||
{
|
||||
if (argumentsOpt != null)
|
||||
{
|
||||
NextArgument();
|
||||
arguments.Append(argumentsOpt);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
void NextCommand()
|
||||
{
|
||||
if (firstCommand)
|
||||
firstCommand = false;
|
||||
else
|
||||
arguments.Append(" &&");
|
||||
}
|
||||
|
||||
public CommandBuilder RunCommand(string exe, string argumentsOpt = null)
|
||||
{
|
||||
var (exe0, arg0) =
|
||||
escapingMode == EscapeMode.Process && exe.EndsWith(".exe", System.StringComparison.Ordinal)
|
||||
? ("mono", exe) // Linux
|
||||
: (exe, null);
|
||||
|
||||
NextCommand();
|
||||
if (executable == null)
|
||||
{
|
||||
executable = exe0;
|
||||
}
|
||||
else
|
||||
{
|
||||
QuoteArgument(exe0);
|
||||
}
|
||||
Argument(arg0);
|
||||
Argument(argumentsOpt);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a build script that contains just this command.
|
||||
/// </summary>
|
||||
public BuildScript Script => BuildScript.Create(executable, arguments.ToString(), workingDirectory, environment);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,268 @@
|
|||
using System;
|
||||
using Semmle.Util.Logging;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Semmle.Autobuild
|
||||
{
|
||||
/// <summary>
|
||||
/// A build rule where the build command is of the form "dotnet build".
|
||||
/// Currently unused because the tracer does not work with dotnet.
|
||||
/// </summary>
|
||||
class DotNetRule : IBuildRule
|
||||
{
|
||||
public BuildScript Analyse(Autobuilder builder)
|
||||
{
|
||||
builder.Log(Severity.Info, "Attempting to build using .NET Core");
|
||||
|
||||
var projects = builder.SolutionsToBuild.Any()
|
||||
? builder.SolutionsToBuild.SelectMany(s => s.Projects).ToArray()
|
||||
: builder.GetExtensions(Language.CSharp.ProjectExtension).Select(p => new Project(builder, p)).ToArray();
|
||||
|
||||
var notDotNetProject = projects.FirstOrDefault(p => !p.DotNetProject);
|
||||
if (notDotNetProject != null)
|
||||
{
|
||||
builder.Log(Severity.Info, "Not using .NET Core because of incompatible project {0}", notDotNetProject);
|
||||
return BuildScript.Failure;
|
||||
}
|
||||
|
||||
if (!builder.SolutionsToBuild.Any())
|
||||
// Attempt dotnet build in root folder
|
||||
return WithDotNet(builder, dotNet =>
|
||||
{
|
||||
var info = GetInfoCommand(builder.Actions, dotNet);
|
||||
var clean = GetCleanCommand(builder.Actions, dotNet).Script;
|
||||
var restore = GetRestoreCommand(builder.Actions, dotNet).Script;
|
||||
var build = GetBuildCommand(builder, dotNet).Script;
|
||||
return info & clean & BuildScript.Try(restore) & build;
|
||||
});
|
||||
|
||||
// Attempt dotnet build on each solution
|
||||
return WithDotNet(builder, dotNet =>
|
||||
{
|
||||
var ret = GetInfoCommand(builder.Actions, dotNet);
|
||||
foreach (var solution in builder.SolutionsToBuild)
|
||||
{
|
||||
var cleanCommand = GetCleanCommand(builder.Actions, dotNet);
|
||||
cleanCommand.QuoteArgument(solution.Path);
|
||||
var clean = cleanCommand.Script;
|
||||
|
||||
var restoreCommand = GetRestoreCommand(builder.Actions, dotNet);
|
||||
restoreCommand.QuoteArgument(solution.Path);
|
||||
var restore = restoreCommand.Script;
|
||||
|
||||
var buildCommand = GetBuildCommand(builder, dotNet);
|
||||
buildCommand.QuoteArgument(solution.Path);
|
||||
var build = buildCommand.Script;
|
||||
|
||||
ret &= clean & BuildScript.Try(restore) & build;
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a script that attempts to download relevant version(s) of the
|
||||
/// .NET Core SDK, followed by running the script generated by <paramref name="f"/>.
|
||||
///
|
||||
/// The first element <code>DotNetPath</code> of the argument to <paramref name="f"/>
|
||||
/// is the path where .NET Core was installed, and the second element <code>Environment</code>
|
||||
/// is any additional required environment variables. The tuple argument is <code>null</code>
|
||||
/// when the installation failed.
|
||||
/// </summary>
|
||||
public static BuildScript WithDotNet(Autobuilder builder, Func<(string DotNetPath, IDictionary<string, string> Environment)?, BuildScript> f)
|
||||
{
|
||||
var installDir = builder.Actions.PathCombine(builder.Options.RootDirectory, ".dotnet");
|
||||
var installScript = DownloadDotNet(builder, installDir);
|
||||
return BuildScript.Bind(installScript, installed =>
|
||||
{
|
||||
if (installed == 0)
|
||||
{
|
||||
// The installation succeeded, so use the newly installed .NET Core
|
||||
var path = builder.Actions.GetEnvironmentVariable("PATH");
|
||||
var delim = builder.Actions.IsWindows() ? ";" : ":";
|
||||
var env = new Dictionary<string, string>{
|
||||
{ "DOTNET_MULTILEVEL_LOOKUP", "false" }, // prevent look up of other .NET Core SDKs
|
||||
{ "DOTNET_SKIP_FIRST_TIME_EXPERIENCE", "true" },
|
||||
{ "PATH", installDir + delim + path }
|
||||
};
|
||||
return f((installDir, env));
|
||||
}
|
||||
|
||||
return f(null);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a script for downloading relevant versions of the
|
||||
/// .NET Core SDK. The SDK(s) will be installed at <code>installDir</code>
|
||||
/// (provided that the script succeeds).
|
||||
/// </summary>
|
||||
static BuildScript DownloadDotNet(Autobuilder builder, string installDir)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(builder.Options.DotNetVersion))
|
||||
// Specific version supplied in configuration: always use that
|
||||
return DownloadDotNetVersion(builder, installDir, builder.Options.DotNetVersion);
|
||||
|
||||
// Download versions mentioned in `global.json` files
|
||||
// See https://docs.microsoft.com/en-us/dotnet/core/tools/global-json
|
||||
var installScript = BuildScript.Success;
|
||||
var validGlobalJson = false;
|
||||
foreach (var path in builder.Paths.Where(p => p.EndsWith("global.json", StringComparison.Ordinal)))
|
||||
{
|
||||
string version;
|
||||
try
|
||||
{
|
||||
var o = JObject.Parse(File.ReadAllText(path));
|
||||
version = (string)o["sdk"]["version"];
|
||||
}
|
||||
catch
|
||||
{
|
||||
// not a valid global.json file
|
||||
continue;
|
||||
}
|
||||
|
||||
installScript &= DownloadDotNetVersion(builder, installDir, version);
|
||||
validGlobalJson = true;
|
||||
}
|
||||
|
||||
return validGlobalJson ? installScript : BuildScript.Failure;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a script for downloading a specific .NET Core SDK version, if the
|
||||
/// version is not already installed.
|
||||
///
|
||||
/// See https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script.
|
||||
/// </summary>
|
||||
static BuildScript DownloadDotNetVersion(Autobuilder builder, string path, string version)
|
||||
{
|
||||
return BuildScript.Bind(GetInstalledSdksScript(builder.Actions), (sdks, sdksRet) =>
|
||||
{
|
||||
if (sdksRet == 0 && sdks.Count() == 1 && sdks[0].StartsWith(version + " ", StringComparison.Ordinal))
|
||||
// The requested SDK is already installed (and no other SDKs are installed), so
|
||||
// no need to reinstall
|
||||
return BuildScript.Failure;
|
||||
|
||||
builder.Log(Severity.Info, "Attempting to download .NET Core {0}", version);
|
||||
|
||||
if (builder.Actions.IsWindows())
|
||||
{
|
||||
var psScript = @"param([string]$Version, [string]$InstallDir)
|
||||
|
||||
add-type @""
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
public class TrustAllCertsPolicy : ICertificatePolicy
|
||||
{
|
||||
public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
""@
|
||||
$AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols
|
||||
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
|
||||
$Script = Invoke-WebRequest -useb 'https://dot.net/v1/dotnet-install.ps1'
|
||||
|
||||
$arguments = @{
|
||||
Channel = 'release'
|
||||
Version = $Version
|
||||
InstallDir = $InstallDir
|
||||
}
|
||||
|
||||
$ScriptBlock = [scriptblock]::create("".{$($Script)} $(&{$args} @arguments)"")
|
||||
|
||||
Invoke-Command -ScriptBlock $ScriptBlock";
|
||||
var psScriptFile = builder.Actions.PathCombine(builder.Options.RootDirectory, "install-dotnet.ps1");
|
||||
builder.Actions.WriteAllText(psScriptFile, psScript);
|
||||
|
||||
var install = new CommandBuilder(builder.Actions).
|
||||
RunCommand("powershell").
|
||||
Argument("-NoProfile").
|
||||
Argument("-ExecutionPolicy").
|
||||
Argument("unrestricted").
|
||||
Argument("-file").
|
||||
Argument(psScriptFile).
|
||||
Argument("-Version").
|
||||
Argument(version).
|
||||
Argument("-InstallDir").
|
||||
Argument(path);
|
||||
return install.Script;
|
||||
}
|
||||
else
|
||||
{
|
||||
var curl = new CommandBuilder(builder.Actions).
|
||||
RunCommand("curl").
|
||||
Argument("-sO").
|
||||
Argument("https://dot.net/v1/dotnet-install.sh");
|
||||
|
||||
var chmod = new CommandBuilder(builder.Actions).
|
||||
RunCommand("chmod").
|
||||
Argument("u+x").
|
||||
Argument("dotnet-install.sh");
|
||||
|
||||
var install = new CommandBuilder(builder.Actions).
|
||||
RunCommand("./dotnet-install.sh").
|
||||
Argument("--channel").
|
||||
Argument("release").
|
||||
Argument("--version").
|
||||
Argument(version).
|
||||
Argument("--install-dir").
|
||||
Argument(path);
|
||||
|
||||
return curl.Script & chmod.Script & install.Script;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static BuildScript GetInstalledSdksScript(IBuildActions actions)
|
||||
{
|
||||
var listSdks = new CommandBuilder(actions).
|
||||
RunCommand("dotnet").
|
||||
Argument("--list-sdks");
|
||||
return listSdks.Script;
|
||||
}
|
||||
|
||||
static string DotNetCommand(IBuildActions actions, string dotNetPath) =>
|
||||
dotNetPath != null ? actions.PathCombine(dotNetPath, "dotnet") : "dotnet";
|
||||
|
||||
BuildScript GetInfoCommand(IBuildActions actions, (string DotNetPath, IDictionary<string, string> Environment)? arg)
|
||||
{
|
||||
var info = new CommandBuilder(actions, null, arg?.Environment).
|
||||
RunCommand(DotNetCommand(actions, arg?.DotNetPath)).
|
||||
Argument("--info");
|
||||
return info.Script;
|
||||
}
|
||||
|
||||
CommandBuilder GetCleanCommand(IBuildActions actions, (string DotNetPath, IDictionary<string, string> Environment)? arg)
|
||||
{
|
||||
var clean = new CommandBuilder(actions, null, arg?.Environment).
|
||||
RunCommand(DotNetCommand(actions, arg?.DotNetPath)).
|
||||
Argument("clean");
|
||||
return clean;
|
||||
}
|
||||
|
||||
CommandBuilder GetRestoreCommand(IBuildActions actions, (string DotNetPath, IDictionary<string, string> Environment)? arg)
|
||||
{
|
||||
var restore = new CommandBuilder(actions, null, arg?.Environment).
|
||||
RunCommand(DotNetCommand(actions, arg?.DotNetPath)).
|
||||
Argument("restore");
|
||||
return restore;
|
||||
}
|
||||
|
||||
CommandBuilder GetBuildCommand(Autobuilder builder, (string DotNetPath, IDictionary<string, string> Environment)? arg)
|
||||
{
|
||||
var build = new CommandBuilder(builder.Actions, null, arg?.Environment).
|
||||
IndexCommand(builder.Odasa, DotNetCommand(builder.Actions, arg?.DotNetPath)).
|
||||
Argument("build").
|
||||
Argument("--no-incremental").
|
||||
Argument("/p:UseSharedCompilation=false").
|
||||
Argument(builder.Options.DotNetArguments);
|
||||
return build;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
namespace Semmle.Autobuild
|
||||
{
|
||||
public sealed class Language
|
||||
{
|
||||
public static readonly Language Cpp = new Language(".vcxproj");
|
||||
public static readonly Language CSharp = new Language(".csproj");
|
||||
|
||||
public bool ProjectFileHasThisLanguage(string path) =>
|
||||
System.IO.Path.GetExtension(path) == ProjectExtension;
|
||||
|
||||
public static bool IsProjectFileForAnySupportedLanguage(string path) =>
|
||||
Cpp.ProjectFileHasThisLanguage(path) || CSharp.ProjectFileHasThisLanguage(path);
|
||||
|
||||
public readonly string ProjectExtension;
|
||||
|
||||
private Language(string extension)
|
||||
{
|
||||
ProjectExtension = extension;
|
||||
}
|
||||
|
||||
public override string ToString() =>
|
||||
ProjectExtension == Cpp.ProjectExtension ? "C/C++" : "C#";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
using Semmle.Util.Logging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Semmle.Autobuild
|
||||
{
|
||||
/// <summary>
|
||||
/// A build rule using msbuild.
|
||||
/// </summary>
|
||||
class MsBuildRule : IBuildRule
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the msbuild command.
|
||||
/// </summary>
|
||||
const string MsBuild = "msbuild";
|
||||
|
||||
public BuildScript Analyse(Autobuilder builder)
|
||||
{
|
||||
builder.Log(Severity.Info, "Attempting to build using MSBuild");
|
||||
|
||||
if (!builder.SolutionsToBuild.Any())
|
||||
{
|
||||
builder.Log(Severity.Info, "Could not find a suitable solution file to build");
|
||||
return BuildScript.Failure;
|
||||
}
|
||||
|
||||
var vsTools = GetVcVarsBatFile(builder);
|
||||
|
||||
if (vsTools == null && builder.SolutionsToBuild.Any())
|
||||
{
|
||||
vsTools = BuildTools.FindCompatibleVcVars(builder.Actions, builder.SolutionsToBuild.First());
|
||||
}
|
||||
|
||||
if (vsTools == null && builder.Actions.IsWindows())
|
||||
{
|
||||
builder.Log(Severity.Warning, "Could not find a suitable version of vcvarsall.bat");
|
||||
}
|
||||
|
||||
var nuget = builder.Actions.PathCombine(builder.SemmlePlatformTools, "csharp", "nuget", "nuget.exe");
|
||||
|
||||
var ret = BuildScript.Success;
|
||||
|
||||
foreach (var solution in builder.SolutionsToBuild)
|
||||
{
|
||||
if (builder.Options.NugetRestore)
|
||||
{
|
||||
var nugetCommand = new CommandBuilder(builder.Actions).
|
||||
RunCommand(nuget).
|
||||
Argument("restore").
|
||||
QuoteArgument(solution.Path);
|
||||
ret &= BuildScript.Try(nugetCommand.Script);
|
||||
}
|
||||
|
||||
var command = new CommandBuilder(builder.Actions);
|
||||
|
||||
if (vsTools != null)
|
||||
{
|
||||
command.CallBatFile(vsTools.Path);
|
||||
}
|
||||
|
||||
command.IndexCommand(builder.Odasa, MsBuild);
|
||||
command.QuoteArgument(solution.Path);
|
||||
|
||||
command.Argument("/p:UseSharedCompilation=false");
|
||||
|
||||
string target = builder.Options.MsBuildTarget != null ? builder.Options.MsBuildTarget : "rebuild";
|
||||
string platform = builder.Options.MsBuildPlatform != null ? builder.Options.MsBuildPlatform : solution.DefaultPlatformName;
|
||||
string configuration = builder.Options.MsBuildConfiguration != null ? builder.Options.MsBuildConfiguration : solution.DefaultConfigurationName;
|
||||
|
||||
command.Argument("/t:" + target);
|
||||
command.Argument(string.Format("/p:Platform=\"{0}\"", platform));
|
||||
command.Argument(string.Format("/p:Configuration=\"{0}\"", configuration));
|
||||
command.Argument("/p:MvcBuildViews=true");
|
||||
|
||||
command.Argument(builder.Options.MsBuildArguments);
|
||||
|
||||
ret &= command.Script;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the BAT file used to initialize the appropriate Visual Studio
|
||||
/// version/platform, as specified by the `vstools_version` property in
|
||||
/// lgtm.yml.
|
||||
///
|
||||
/// Returns <code>null</code> when no version is specified.
|
||||
/// </summary>
|
||||
public static VcVarsBatFile GetVcVarsBatFile(Autobuilder builder)
|
||||
{
|
||||
VcVarsBatFile vsTools = null;
|
||||
|
||||
if (builder.Options.VsToolsVersion != null)
|
||||
{
|
||||
if (int.TryParse(builder.Options.VsToolsVersion, out var msToolsVersion))
|
||||
{
|
||||
foreach (var b in BuildTools.VcVarsAllBatFiles(builder.Actions))
|
||||
{
|
||||
builder.Log(Severity.Info, "Found {0} version {1}", b.Path, b.ToolsVersion);
|
||||
}
|
||||
|
||||
vsTools = BuildTools.FindCompatibleVcVars(builder.Actions, msToolsVersion);
|
||||
if (vsTools == null)
|
||||
builder.Log(Severity.Warning, "Could not find build tools matching version {0}", msToolsVersion);
|
||||
else
|
||||
builder.Log(Severity.Info, "Setting Visual Studio tools to {0}", vsTools.Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Log(Severity.Error, "The format of vstools_version is incorrect. Please specify an integer.");
|
||||
}
|
||||
}
|
||||
|
||||
return vsTools;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace Semmle.Autobuild
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static int Main()
|
||||
{
|
||||
var options = new AutobuildOptions();
|
||||
var actions = SystemBuildActions.Instance;
|
||||
|
||||
try
|
||||
{
|
||||
options.ReadEnvironment(actions);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
Console.WriteLine("The value \"{0}\" for parameter \"{1}\" is invalid", ex.ActualValue, ex.ParamName);
|
||||
}
|
||||
|
||||
var builder = new Autobuilder(actions, options);
|
||||
|
||||
Console.WriteLine($"Semmle autobuilder for {options.Language}");
|
||||
|
||||
return builder.AttemptBuild();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using Semmle.Util.Logging;
|
||||
|
||||
namespace Semmle.Autobuild
|
||||
{
|
||||
/// <summary>
|
||||
/// Representation of a .csproj (C#) or .vcxproj (C++) file.
|
||||
/// C# project files come in 2 flavours, .Net core and msbuild, but they
|
||||
/// have the same file extension.
|
||||
/// </summary>
|
||||
public class Project
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds if this project is for .Net core.
|
||||
/// </summary>
|
||||
public bool DotNetProject { get; private set; }
|
||||
|
||||
public bool ValidToolsVersion { get; private set; }
|
||||
|
||||
public Version ToolsVersion { get; private set; }
|
||||
|
||||
readonly string filename;
|
||||
|
||||
public Project(Autobuilder builder, string filename)
|
||||
{
|
||||
this.filename = filename;
|
||||
ToolsVersion = new Version();
|
||||
|
||||
if (!File.Exists(filename))
|
||||
return;
|
||||
|
||||
var projFile = new XmlDocument();
|
||||
projFile.Load(filename);
|
||||
var root = projFile.DocumentElement;
|
||||
|
||||
if (root.Name == "Project")
|
||||
{
|
||||
if (root.HasAttribute("Sdk"))
|
||||
{
|
||||
DotNetProject = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var toolsVersion = root.GetAttribute("ToolsVersion");
|
||||
if (string.IsNullOrEmpty(toolsVersion))
|
||||
{
|
||||
builder.Log(Severity.Warning, "Project {0} is missing a tools version", filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
ToolsVersion = new Version(toolsVersion);
|
||||
ValidToolsVersion = true;
|
||||
}
|
||||
catch // Generic catch clause - Version constructor throws about 5 different exceptions.
|
||||
{
|
||||
builder.Log(Severity.Warning, "Project {0} has invalid tools version {1}", filename, toolsVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => filename;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Semmle.Autobuild")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Semmle")]
|
||||
[assembly: AssemblyProduct("Semmle Visual Studio Autobuild")]
|
||||
[assembly: AssemblyCopyright("Copyright © Semmle 2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("1d9920ad-7b00-4df1-8b01-9ff5b687828e")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -0,0 +1,27 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<AssemblyName>Semmle.Autobuild</AssemblyName>
|
||||
<RootNamespace>Semmle.Autobuild</RootNamespace>
|
||||
<ApplicationIcon />
|
||||
<OutputType>Exe</OutputType>
|
||||
<StartupObject />
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build" Version="15.8.166" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Semmle.Util\Semmle.Util.csproj" />
|
||||
<ProjectReference Include="..\Semmle.Extraction.CSharp\Semmle.Extraction.CSharp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,107 @@
|
|||
using Microsoft.Build.Construction;
|
||||
using Microsoft.Build.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Semmle.Util;
|
||||
|
||||
namespace Semmle.Autobuild
|
||||
{
|
||||
/// <summary>
|
||||
/// A solution file, extension .sln.
|
||||
/// </summary>
|
||||
public interface ISolution
|
||||
{
|
||||
/// <summary>
|
||||
/// List of C# or C++ projects contained in the solution.
|
||||
/// (There could be other project types as well - these are ignored.)
|
||||
/// </summary>
|
||||
|
||||
IEnumerable<Project> Projects { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Solution configurations.
|
||||
/// </summary>
|
||||
IEnumerable<SolutionConfigurationInSolution> Configurations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The default configuration name, e.g. "Release"
|
||||
/// </summary>
|
||||
string DefaultConfigurationName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The default platform name, e.g. "x86"
|
||||
/// </summary>
|
||||
string DefaultPlatformName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The path of the solution file.
|
||||
/// </summary>
|
||||
string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of C# or C++ projects.
|
||||
/// </summary>
|
||||
int ProjectCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the "best" tools version for this solution.
|
||||
/// If there are several versions, because the project files
|
||||
/// are inconsistent, then pick the highest/latest version.
|
||||
/// If no tools versions are present, return 0.0.0.0.
|
||||
/// </summary>
|
||||
Version ToolsVersion { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A solution file on the filesystem, read using Microsoft.Build.
|
||||
/// </summary>
|
||||
class Solution : ISolution
|
||||
{
|
||||
readonly SolutionFile solution;
|
||||
|
||||
public IEnumerable<Project> Projects { get; private set; }
|
||||
|
||||
public IEnumerable<SolutionConfigurationInSolution> Configurations =>
|
||||
solution == null ? Enumerable.Empty<SolutionConfigurationInSolution>() : solution.SolutionConfigurations;
|
||||
|
||||
public string DefaultConfigurationName =>
|
||||
solution == null ? "" : solution.GetDefaultConfigurationName();
|
||||
|
||||
public string DefaultPlatformName =>
|
||||
solution == null ? "" : solution.GetDefaultPlatformName();
|
||||
|
||||
public Solution(Autobuilder builder, string path)
|
||||
{
|
||||
Path = System.IO.Path.GetFullPath(path);
|
||||
try
|
||||
{
|
||||
solution = SolutionFile.Parse(Path);
|
||||
|
||||
Projects =
|
||||
solution.ProjectsInOrder.
|
||||
Where(p => p.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat).
|
||||
Select(p => System.IO.Path.GetFullPath(FileUtils.ConvertToNative(p.AbsolutePath))).
|
||||
Where(p => builder.Options.Language.ProjectFileHasThisLanguage(p)).
|
||||
Select(p => new Project(builder, p)).
|
||||
ToArray();
|
||||
}
|
||||
catch (InvalidProjectFileException)
|
||||
{
|
||||
// We allow specifying projects as solutions in lgtm.yml, so model
|
||||
// that scenario as a solution with just that one project
|
||||
Projects = Language.IsProjectFileForAnySupportedLanguage(Path)
|
||||
? new[] { new Project(builder, Path) }
|
||||
: new Project[0];
|
||||
}
|
||||
}
|
||||
|
||||
public string Path { get; private set; }
|
||||
|
||||
public int ProjectCount => Projects.Count();
|
||||
|
||||
IEnumerable<Version> ToolsVersions => Projects.Where(p => p.ValidToolsVersion).Select(p => p.ToolsVersion);
|
||||
|
||||
public Version ToolsVersion => ToolsVersions.Any() ? ToolsVersions.Max() : new Version();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
using System.IO;
|
||||
|
||||
namespace Semmle.Autobuild
|
||||
{
|
||||
/// <summary>
|
||||
/// Build using standalone extraction.
|
||||
/// </summary>
|
||||
class StandaloneBuildRule : IBuildRule
|
||||
{
|
||||
public BuildScript Analyse(Autobuilder builder)
|
||||
{
|
||||
BuildScript GetCommand(string solution)
|
||||
{
|
||||
var standalone = builder.Actions.PathCombine(builder.SemmlePlatformTools, "csharp", "Semmle.Extraction.CSharp.Standalone");
|
||||
var cmd = new CommandBuilder(builder.Actions);
|
||||
cmd.RunCommand(standalone);
|
||||
|
||||
if (solution != null)
|
||||
cmd.QuoteArgument(solution);
|
||||
|
||||
cmd.Argument("--references:.");
|
||||
|
||||
if (!builder.Options.NugetRestore)
|
||||
{
|
||||
cmd.Argument("--skip-nuget");
|
||||
}
|
||||
|
||||
return cmd.Script;
|
||||
}
|
||||
|
||||
if (!builder.Options.Buildless)
|
||||
return BuildScript.Failure;
|
||||
|
||||
var solutions = builder.Options.Solution.Length;
|
||||
|
||||
if (solutions == 0)
|
||||
return GetCommand(null);
|
||||
|
||||
var script = BuildScript.Success;
|
||||
foreach (var solution in builder.Options.Solution)
|
||||
script &= GetCommand(solution);
|
||||
|
||||
return script;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Semmle.Autobuild
|
||||
{
|
||||
/// <summary>
|
||||
/// XML extraction.
|
||||
/// </summary>
|
||||
class XmlBuildRule : IBuildRule
|
||||
{
|
||||
public BuildScript Analyse(Autobuilder builder)
|
||||
{
|
||||
var command = new CommandBuilder(builder.Actions).
|
||||
RunCommand(builder.Odasa).
|
||||
Argument("index --xml --extensions config");
|
||||
return command.Script;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,267 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Reflection.PortableExecutable;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Globalization;
|
||||
using Semmle.Util.Logging;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Driver
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about a single assembly.
|
||||
/// In particular, provides references between assemblies.
|
||||
/// </summary>
|
||||
class AssemblyInfo
|
||||
{
|
||||
public override string ToString() => filename;
|
||||
|
||||
static AssemblyName CreateAssemblyName(MetadataReader mdReader, StringHandle name, System.Version version, StringHandle culture)
|
||||
{
|
||||
var cultureString = mdReader.GetString(culture);
|
||||
|
||||
var assemblyName = new AssemblyName()
|
||||
{
|
||||
Name = mdReader.GetString(name),
|
||||
Version = version
|
||||
};
|
||||
|
||||
if (cultureString != "neutral")
|
||||
assemblyName.CultureInfo = CultureInfo.GetCultureInfo(cultureString);
|
||||
|
||||
return assemblyName;
|
||||
}
|
||||
|
||||
static AssemblyName CreateAssemblyName(MetadataReader mdReader, AssemblyReference ar)
|
||||
{
|
||||
var an = CreateAssemblyName(mdReader, ar.Name, ar.Version, ar.Culture);
|
||||
if (!ar.PublicKeyOrToken.IsNil)
|
||||
an.SetPublicKeyToken(mdReader.GetBlobBytes(ar.PublicKeyOrToken));
|
||||
return an;
|
||||
}
|
||||
|
||||
static AssemblyName CreateAssemblyName(MetadataReader mdReader, AssemblyDefinition ad)
|
||||
{
|
||||
var an = CreateAssemblyName(mdReader, ad.Name, ad.Version, ad.Culture);
|
||||
if (!ad.PublicKey.IsNil)
|
||||
an.SetPublicKey(mdReader.GetBlobBytes(ad.PublicKey));
|
||||
return an;
|
||||
}
|
||||
|
||||
public AssemblyInfo(string path)
|
||||
{
|
||||
filename = path;
|
||||
|
||||
// Attempt to open the file and see if it's a valid assembly.
|
||||
using (var stream = File.OpenRead(path))
|
||||
using (var peReader = new PEReader(stream))
|
||||
{
|
||||
try
|
||||
{
|
||||
isAssembly = peReader.HasMetadata;
|
||||
if (!isAssembly) return;
|
||||
|
||||
var mdReader = peReader.GetMetadataReader();
|
||||
|
||||
isAssembly = mdReader.IsAssembly;
|
||||
if (!mdReader.IsAssembly) return;
|
||||
|
||||
// Get our own assembly name
|
||||
name = CreateAssemblyName(mdReader, mdReader.GetAssemblyDefinition());
|
||||
|
||||
references = mdReader.AssemblyReferences.
|
||||
Select(r => mdReader.GetAssemblyReference(r)).
|
||||
Select(ar => CreateAssemblyName(mdReader, ar)).
|
||||
ToArray();
|
||||
}
|
||||
catch (System.BadImageFormatException)
|
||||
{
|
||||
// This failed on one of the Roslyn tests that includes
|
||||
// a deliberately malformed assembly.
|
||||
// In this case, we just skip the extraction of this assembly.
|
||||
isAssembly = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly AssemblyName name;
|
||||
public readonly string filename;
|
||||
public bool extract;
|
||||
public readonly bool isAssembly;
|
||||
public readonly AssemblyName[] references;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to manage a collection of assemblies.
|
||||
/// Resolves references between assemblies and determines which
|
||||
/// additional assemblies need to be extracted.
|
||||
/// </summary>
|
||||
class AssemblyList
|
||||
{
|
||||
class AssemblyNameComparer : IEqualityComparer<AssemblyName>
|
||||
{
|
||||
bool IEqualityComparer<AssemblyName>.Equals(AssemblyName x, AssemblyName y) =>
|
||||
x.Name == y.Name && x.Version == y.Version;
|
||||
|
||||
int IEqualityComparer<AssemblyName>.GetHashCode(AssemblyName obj) =>
|
||||
obj.Name.GetHashCode() + 7 * obj.Version.GetHashCode();
|
||||
}
|
||||
|
||||
readonly Dictionary<AssemblyName, AssemblyInfo> assembliesRead = new Dictionary<AssemblyName, AssemblyInfo>(new AssemblyNameComparer());
|
||||
|
||||
public void AddFile(string assemblyPath, bool extractAll)
|
||||
{
|
||||
if (!filesAnalyzed.Contains(assemblyPath))
|
||||
{
|
||||
filesAnalyzed.Add(assemblyPath);
|
||||
var info = new AssemblyInfo(assemblyPath);
|
||||
if (info.isAssembly)
|
||||
{
|
||||
info.extract = extractAll;
|
||||
if (!assembliesRead.ContainsKey(info.name))
|
||||
assembliesRead.Add(info.name, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<AssemblyInfo> AssembliesToExtract => assembliesRead.Values.Where(info => info.extract);
|
||||
|
||||
IEnumerable<AssemblyName> AssembliesToReference => AssembliesToExtract.SelectMany(info => info.references);
|
||||
|
||||
public void ResolveReferences()
|
||||
{
|
||||
var assembliesToReference = new Stack<AssemblyName>(AssembliesToReference);
|
||||
|
||||
while (assembliesToReference.Any())
|
||||
{
|
||||
var item = assembliesToReference.Pop();
|
||||
AssemblyInfo info;
|
||||
if (assembliesRead.TryGetValue(item, out info))
|
||||
{
|
||||
if (!info.extract)
|
||||
{
|
||||
info.extract = true;
|
||||
foreach (var reference in info.references)
|
||||
assembliesToReference.Push(reference);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
missingReferences.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly HashSet<string> filesAnalyzed = new HashSet<string>();
|
||||
public readonly HashSet<AssemblyName> missingReferences = new HashSet<AssemblyName>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the command line and collates a list of DLLs/EXEs to extract.
|
||||
/// </summary>
|
||||
class ExtractorOptions
|
||||
{
|
||||
readonly AssemblyList assemblyList = new AssemblyList();
|
||||
|
||||
public void AddDirectory(string directory, bool extractAll)
|
||||
{
|
||||
foreach (var file in
|
||||
Directory.EnumerateFiles(directory, "*.dll", SearchOption.AllDirectories).
|
||||
Concat(Directory.EnumerateFiles(directory, "*.exe", SearchOption.AllDirectories)))
|
||||
{
|
||||
assemblyList.AddFile(file, extractAll);
|
||||
}
|
||||
}
|
||||
|
||||
void AddFrameworkDirectories(bool extractAll)
|
||||
{
|
||||
AddDirectory(RuntimeEnvironment.GetRuntimeDirectory(), extractAll);
|
||||
}
|
||||
|
||||
public Verbosity Verbosity { get; private set; }
|
||||
public bool NoCache { get; private set; }
|
||||
public int Threads { get; private set; }
|
||||
public bool PDB { get; private set; }
|
||||
|
||||
void AddFileOrDirectory(string path)
|
||||
{
|
||||
path = Path.GetFullPath(path);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
assemblyList.AddFile(path, true);
|
||||
AddDirectory(Path.GetDirectoryName(path), false);
|
||||
}
|
||||
else if (Directory.Exists(path))
|
||||
{
|
||||
AddDirectory(path, true);
|
||||
}
|
||||
}
|
||||
|
||||
void ResolveReferences()
|
||||
{
|
||||
assemblyList.ResolveReferences();
|
||||
AssembliesToExtract = assemblyList.AssembliesToExtract.ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable<AssemblyInfo> AssembliesToExtract { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assemblies that were referenced but were not available to be
|
||||
/// extracted. This is not an error, it just means that the database is not
|
||||
/// as complete as it could be.
|
||||
/// </summary>
|
||||
public IEnumerable<AssemblyName> MissingReferences => assemblyList.missingReferences;
|
||||
|
||||
public static ExtractorOptions ParseCommandLine(string[] args)
|
||||
{
|
||||
var options = new ExtractorOptions();
|
||||
options.Verbosity = Verbosity.Info;
|
||||
options.Threads = System.Environment.ProcessorCount;
|
||||
options.PDB = true;
|
||||
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (arg == "--verbose")
|
||||
{
|
||||
options.Verbosity = Verbosity.All;
|
||||
}
|
||||
else if (arg == "--silent")
|
||||
{
|
||||
options.Verbosity = Verbosity.Off;
|
||||
}
|
||||
else if (arg.StartsWith("--verbosity:"))
|
||||
{
|
||||
options.Verbosity = (Verbosity)int.Parse(arg.Substring(12));
|
||||
}
|
||||
else if (arg == "--dotnet")
|
||||
{
|
||||
options.AddFrameworkDirectories(true);
|
||||
}
|
||||
else if (arg == "--nocache")
|
||||
{
|
||||
options.NoCache = true;
|
||||
}
|
||||
else if (arg.StartsWith("--threads:"))
|
||||
{
|
||||
options.Threads = int.Parse(arg.Substring(10));
|
||||
}
|
||||
else if (arg == "--no-pdb")
|
||||
{
|
||||
options.PDB = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
options.AddFileOrDirectory(arg);
|
||||
}
|
||||
}
|
||||
|
||||
options.AddFrameworkDirectories(false);
|
||||
options.ResolveReferences();
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using Semmle.Util.Logging;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Driver
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void DisplayHelp()
|
||||
{
|
||||
Console.WriteLine("CIL command line extractor");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Usage: Semmle.Extraction.CIL.Driver.exe [options] path ...");
|
||||
Console.WriteLine(" --verbose Turn on verbose output");
|
||||
Console.WriteLine(" --dotnet Extract the .Net Framework");
|
||||
Console.WriteLine(" --nocache Overwrite existing trap files");
|
||||
Console.WriteLine(" --no-pdb Do not extract PDB files");
|
||||
Console.WriteLine(" path A directory/dll/exe to analyze");
|
||||
}
|
||||
|
||||
static void ExtractAssembly(Layout layout, string assemblyPath, ILogger logger, bool nocache, bool extractPdbs)
|
||||
{
|
||||
string trapFile;
|
||||
bool extracted;
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
Entities.Assembly.ExtractCIL(layout, assemblyPath, logger, nocache, extractPdbs, out trapFile, out extracted);
|
||||
sw.Stop();
|
||||
logger.Log(Severity.Info, " {0} ({1})", assemblyPath, sw.Elapsed);
|
||||
}
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
DisplayHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
var options = ExtractorOptions.ParseCommandLine(args);
|
||||
var layout = new Layout();
|
||||
var logger = new ConsoleLogger(options.Verbosity);
|
||||
|
||||
var actions = options.
|
||||
AssembliesToExtract.Select(asm => asm.filename).
|
||||
Select<string, Action>(filename => () => ExtractAssembly(layout, filename, logger, options.NoCache, options.PDB)).
|
||||
ToArray();
|
||||
|
||||
foreach (var missingRef in options.MissingReferences)
|
||||
logger.Log(Severity.Info, " Missing assembly " + missingRef);
|
||||
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
var piOptions = new ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = options.Threads
|
||||
};
|
||||
|
||||
Parallel.Invoke(piOptions, actions);
|
||||
|
||||
sw.Stop();
|
||||
logger.Log(Severity.Info, "Extraction completed in {0}", sw.Elapsed);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Semmle.Extraction.CIL.Driver")]
|
||||
[assembly: AssemblyDescription("Semmle CIL extractor")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Semmle Ltd")]
|
||||
[assembly: AssemblyProduct("Semmle.Extraction.CIL.Driver")]
|
||||
[assembly: AssemblyCopyright("Copyright © Semmle 2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("5642ae68-9c26-43c9-bd3c-49923dddf02d")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -0,0 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<AssemblyName>Semmle.Extraction.CIL.Driver</AssemblyName>
|
||||
<RootNamespace>Semmle.Extraction.CIL.Driver</RootNamespace>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Semmle.Extraction.CIL\Semmle.Extraction.CIL.csproj" />
|
||||
<ProjectReference Include="..\Semmle.Extraction\Semmle.Extraction.csproj" />
|
||||
<ProjectReference Include="..\Semmle.Util\Semmle.Util.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Semmle.Extraction.CIL
|
||||
{
|
||||
/// <summary>
|
||||
/// A factory and a cache for mapping source entities to target entities.
|
||||
/// Could be considered as a memoizer.
|
||||
/// </summary>
|
||||
/// <typeparam name="SrcType">The type of the source.</typeparam>
|
||||
/// <typeparam name="TargetType">The type of the generated object.</typeparam>
|
||||
public class CachedFunction<SrcType, TargetType>
|
||||
{
|
||||
readonly Func<SrcType, TargetType> generator;
|
||||
readonly Dictionary<SrcType, TargetType> cache;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the factory with a given mapping.
|
||||
/// </summary>
|
||||
/// <param name="g">The mapping.</param>
|
||||
public CachedFunction(Func<SrcType, TargetType> g)
|
||||
{
|
||||
generator = g;
|
||||
cache = new Dictionary<SrcType, TargetType>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target for a given source.
|
||||
/// Create it if it does not exist.
|
||||
/// </summary>
|
||||
/// <param name="src">The source object.</param>
|
||||
/// <returns>The created object.</returns>
|
||||
public TargetType this[SrcType src]
|
||||
{
|
||||
get
|
||||
{
|
||||
TargetType result;
|
||||
if (!cache.TryGetValue(src, out result))
|
||||
{
|
||||
result = generator(src);
|
||||
cache[src] = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A factory for mapping a pair of source entities to a target entity.
|
||||
/// </summary>
|
||||
/// <typeparam name="Src1">Source entity type 1.</typeparam>
|
||||
/// <typeparam name="Src2">Source entity type 2.</typeparam>
|
||||
/// <typeparam name="Target">The target type.</typeparam>
|
||||
public class CachedFunction<Src1, Src2, Target>
|
||||
{
|
||||
readonly CachedFunction<(Src1, Src2), Target> factory;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the factory with a given mapping.
|
||||
/// </summary>
|
||||
/// <param name="g">The mapping.</param>
|
||||
public CachedFunction(Func<Src1, Src2, Target> g)
|
||||
{
|
||||
factory = new CachedFunction<(Src1, Src2), Target>(p => g(p.Item1, p.Item2));
|
||||
}
|
||||
|
||||
public Target this[Src1 s1, Src2 s2] => factory[(s1, s2)];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Reflection.PortableExecutable;
|
||||
|
||||
namespace Semmle.Extraction.CIL
|
||||
{
|
||||
/// <summary>
|
||||
/// Extraction context for CIL extraction.
|
||||
/// Adds additional context that is specific for CIL extraction.
|
||||
/// One context = one DLL/EXE.
|
||||
/// </summary>
|
||||
partial class Context : IDisposable
|
||||
{
|
||||
public Extraction.Context cx;
|
||||
readonly FileStream stream;
|
||||
public readonly MetadataReader mdReader;
|
||||
public readonly PEReader peReader;
|
||||
public readonly string assemblyPath;
|
||||
public Entities.Assembly assembly;
|
||||
public PDB.IPdb pdb;
|
||||
|
||||
public Context(Extraction.Context cx, string assemblyPath, bool extractPdbs)
|
||||
{
|
||||
this.cx = cx;
|
||||
this.assemblyPath = assemblyPath;
|
||||
stream = File.OpenRead(assemblyPath);
|
||||
peReader = new PEReader(stream, PEStreamOptions.PrefetchEntireImage);
|
||||
mdReader = peReader.GetMetadataReader();
|
||||
TypeSignatureDecoder = new Entities.TypeSignatureDecoder(this);
|
||||
|
||||
globalNamespace = new Lazy<Entities.Namespace>(() => Populate(new Entities.Namespace(this, GetId(""), null)));
|
||||
systemNamespace = new Lazy<Entities.Namespace>(() => Populate(new Entities.Namespace(this, "System")));
|
||||
genericHandleFactory = new CachedFunction<GenericContext, Handle, ILabelledEntity>(CreateGenericHandle);
|
||||
namespaceFactory = new CachedFunction<StringHandle, Entities.Namespace>(n => CreateNamespace(mdReader.GetString(n)));
|
||||
namespaceDefinitionFactory = new CachedFunction<NamespaceDefinitionHandle, Entities.Namespace>(CreateNamespace);
|
||||
sourceFiles = new CachedFunction<PDB.ISourceFile, Entities.PdbSourceFile>(path => new Entities.PdbSourceFile(this, path));
|
||||
folders = new CachedFunction<string, Entities.Folder>(path => new Entities.Folder(this, path));
|
||||
sourceLocations = new CachedFunction<PDB.Location, Entities.PdbSourceLocation>(location => new Entities.PdbSourceLocation(this, location));
|
||||
|
||||
defaultGenericContext = new EmptyContext(this);
|
||||
|
||||
var def = mdReader.GetAssemblyDefinition();
|
||||
AssemblyPrefix = GetId(def.Name) + "_" + def.Version.ToString() + "::";
|
||||
|
||||
if (extractPdbs)
|
||||
{
|
||||
pdb = PDB.PdbReader.Create(assemblyPath, peReader);
|
||||
if (pdb != null)
|
||||
{
|
||||
cx.Extractor.Logger.Log(Util.Logging.Severity.Info, string.Format("Found PDB information for {0}", assemblyPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (pdb != null)
|
||||
pdb.Dispose();
|
||||
peReader.Dispose();
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract the contents of a given entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to extract.</param>
|
||||
public void Extract(IExtractedEntity entity)
|
||||
{
|
||||
foreach (var content in entity.Contents)
|
||||
{
|
||||
content.Extract(this);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Id AssemblyPrefix;
|
||||
|
||||
public readonly Entities.TypeSignatureDecoder TypeSignatureDecoder;
|
||||
|
||||
/// <summary>
|
||||
/// A type used to signify something we can't handle yet.
|
||||
/// Specifically, function pointers (used in C++).
|
||||
/// </summary>
|
||||
public Entities.Type ErrorType
|
||||
{
|
||||
get
|
||||
{
|
||||
var errorType = new Entities.ErrorType(this);
|
||||
Populate(errorType);
|
||||
return errorType;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to locate debugging information for a particular method.
|
||||
///
|
||||
/// Returns null on failure, for example if there was no PDB information found for the
|
||||
/// DLL, or if the particular method is compiler generated or doesn't come from source code.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the method.</param>
|
||||
/// <returns>The debugging information, or null if the information could not be located.</returns>
|
||||
public PDB.IMethod GetMethodDebugInformation(MethodDefinitionHandle handle)
|
||||
{
|
||||
return pdb == null ? null : pdb.GetMethod(handle.ToDebugInformationHandle());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When we decode a type/method signature, we need access to
|
||||
/// generic parameters.
|
||||
/// </summary>
|
||||
public abstract class GenericContext
|
||||
{
|
||||
public Context cx;
|
||||
|
||||
public GenericContext(Context cx)
|
||||
{
|
||||
this.cx = cx;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of generic type parameters.
|
||||
/// </summary>
|
||||
public abstract IEnumerable<Entities.Type> TypeParameters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of generic method parameters.
|
||||
/// </summary>
|
||||
public abstract IEnumerable<Entities.Type> MethodParameters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the `p`th type parameter.
|
||||
/// </summary>
|
||||
/// <param name="p">The index of the parameter.</param>
|
||||
/// <returns>
|
||||
/// For constructed types, the supplied type.
|
||||
/// For unbound types, the type parameter.
|
||||
/// </returns>
|
||||
public Entities.Type GetGenericTypeParameter(int p)
|
||||
{
|
||||
return TypeParameters.ElementAt(p);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the `p`th method type parameter.
|
||||
/// </summary>
|
||||
/// <param name="p">The index of the parameter.</param>
|
||||
/// <returns>
|
||||
/// For constructed types, the supplied type.
|
||||
/// For unbound types, the type parameter.
|
||||
/// </returns>
|
||||
public Entities.Type GetGenericMethodParameter(int p)
|
||||
{
|
||||
return MethodParameters.ElementAt(p);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A generic context which does not contain any type parameters.
|
||||
/// </summary>
|
||||
public class EmptyContext : GenericContext
|
||||
{
|
||||
public EmptyContext(Context cx) : base(cx)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<Entities.Type> TypeParameters { get { yield break; } }
|
||||
|
||||
public override IEnumerable<Entities.Type> MethodParameters { get { yield break; } }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
using System.Reflection;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using Semmle.Util.Logging;
|
||||
using System;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Entities
|
||||
{
|
||||
public interface ILocation : IEntity
|
||||
{
|
||||
}
|
||||
|
||||
interface IAssembly : ILocation
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An assembly to extract.
|
||||
/// </summary>
|
||||
public class Assembly : LabelledEntity, IAssembly
|
||||
{
|
||||
public override Id IdSuffix => suffix;
|
||||
|
||||
readonly File file;
|
||||
readonly AssemblyName assemblyName;
|
||||
|
||||
public Assembly(Context cx) : base(cx)
|
||||
{
|
||||
cx.assembly = this;
|
||||
var def = cx.mdReader.GetAssemblyDefinition();
|
||||
|
||||
assemblyName = new AssemblyName();
|
||||
assemblyName.Name = cx.mdReader.GetString(def.Name);
|
||||
assemblyName.Version = def.Version;
|
||||
assemblyName.CultureInfo = new CultureInfo(cx.mdReader.GetString(def.Culture));
|
||||
|
||||
if (!def.PublicKey.IsNil)
|
||||
assemblyName.SetPublicKey(cx.mdReader.GetBlobBytes(def.PublicKey));
|
||||
|
||||
ShortId = cx.GetId(assemblyName.FullName) + "#file:///" + cx.assemblyPath.Replace("\\", "/");
|
||||
|
||||
file = new File(cx, cx.assemblyPath);
|
||||
}
|
||||
|
||||
static readonly Id suffix = new StringId(";assembly");
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return file;
|
||||
yield return Tuples.assemblies(this, file, assemblyName.FullName, assemblyName.Name, assemblyName.Version.ToString());
|
||||
|
||||
if (cx.pdb != null)
|
||||
{
|
||||
foreach (var f in cx.pdb.SourceFiles)
|
||||
{
|
||||
yield return cx.CreateSourceFile(f);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var handle in cx.mdReader.TypeDefinitions)
|
||||
{
|
||||
IExtractionProduct product = null;
|
||||
try
|
||||
{
|
||||
product = cx.Create(handle);
|
||||
}
|
||||
catch (InternalError e)
|
||||
{
|
||||
cx.cx.Extractor.Message(new Message
|
||||
{
|
||||
exception = e,
|
||||
message = "Error processing type definition",
|
||||
severity = Semmle.Util.Logging.Severity.Error
|
||||
});
|
||||
}
|
||||
|
||||
// Limitation of C#: Cannot yield return inside a try-catch.
|
||||
if (product != null)
|
||||
yield return product;
|
||||
}
|
||||
|
||||
foreach (var handle in cx.mdReader.MethodDefinitions)
|
||||
{
|
||||
IExtractionProduct product = null;
|
||||
try
|
||||
{
|
||||
product = cx.Create(handle);
|
||||
}
|
||||
catch (InternalError e)
|
||||
{
|
||||
cx.cx.Extractor.Message(new Message
|
||||
{
|
||||
exception = e,
|
||||
message = "Error processing bytecode",
|
||||
severity = Semmle.Util.Logging.Severity.Error
|
||||
});
|
||||
}
|
||||
|
||||
if (product != null)
|
||||
yield return product;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ExtractCIL(Extraction.Context cx, string assemblyPath, bool extractPdbs)
|
||||
{
|
||||
using (var cilContext = new Context(cx, assemblyPath, extractPdbs))
|
||||
{
|
||||
cilContext.Populate(new Assembly(cilContext));
|
||||
cilContext.cx.PopulateAll();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main entry point to the CIL extractor.
|
||||
/// Call this to extract a given assembly.
|
||||
/// </summary>
|
||||
/// <param name="layout">The trap layout.</param>
|
||||
/// <param name="assemblyPath">The full path of the assembly to extract.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="nocache">True to overwrite existing trap file.</param>
|
||||
/// <param name="extractPdbs">Whether to extract PDBs.</param>
|
||||
/// <param name="trapFile">The path of the trap file.</param>
|
||||
/// <param name="extracted">Whether the file was extracted (false=cached).</param>
|
||||
public static void ExtractCIL(Layout layout, string assemblyPath, ILogger logger, bool nocache, bool extractPdbs, out string trapFile, out bool extracted)
|
||||
{
|
||||
trapFile = "";
|
||||
extracted = false;
|
||||
try
|
||||
{
|
||||
var extractor = new Extractor(false, assemblyPath, logger);
|
||||
var project = layout.LookupProjectOrDefault(assemblyPath);
|
||||
using (var trapWriter = project.CreateTrapWriter(logger, assemblyPath + ".cil", true))
|
||||
{
|
||||
trapFile = trapWriter.TrapFile;
|
||||
if (nocache || !System.IO.File.Exists(trapFile))
|
||||
{
|
||||
var cx = new Extraction.Context(extractor, null, trapWriter, null);
|
||||
ExtractCIL(cx, assemblyPath, extractPdbs);
|
||||
extracted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Log(Severity.Error, string.Format("Exception extracting {0}: {1}", assemblyPath, ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// A CIL attribute.
|
||||
/// </summary>
|
||||
interface IAttribute : IExtractedEntity
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entity representing a CIL attribute.
|
||||
/// </summary>
|
||||
class Attribute : UnlabelledEntity, IAttribute
|
||||
{
|
||||
readonly CustomAttribute attrib;
|
||||
readonly IEntity @object;
|
||||
|
||||
public Attribute(Context cx, IEntity @object, CustomAttributeHandle handle) : base(cx)
|
||||
{
|
||||
attrib = cx.mdReader.GetCustomAttribute(handle);
|
||||
this.@object = @object;
|
||||
}
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
var constructor = (Method)cx.Create(attrib.Constructor);
|
||||
yield return constructor;
|
||||
|
||||
yield return Tuples.cil_attribute(this, @object, constructor);
|
||||
|
||||
CustomAttributeValue<Type> decoded;
|
||||
|
||||
try
|
||||
{
|
||||
decoded = attrib.DecodeValue(new CustomAttributeDecoder(cx));
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
{
|
||||
// Attribute decoding is only partial at this stage.
|
||||
yield break;
|
||||
}
|
||||
|
||||
for (int index = 0; index < decoded.FixedArguments.Length; ++index)
|
||||
{
|
||||
object value = decoded.FixedArguments[index].Value;
|
||||
yield return Tuples.cil_attribute_positional_argument(this, index, value == null ? "null" : value.ToString());
|
||||
}
|
||||
|
||||
foreach (var p in decoded.NamedArguments)
|
||||
{
|
||||
object value = p.Value;
|
||||
yield return Tuples.cil_attribute_named_argument(this, p.Name, value == null ? "null" : value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<IExtractionProduct> Populate(Context cx, IEntity @object, CustomAttributeHandleCollection attributes)
|
||||
{
|
||||
foreach (var attrib in attributes)
|
||||
{
|
||||
yield return new Attribute(cx, @object, attrib);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to decode the attribute structure.
|
||||
/// Note that there are some unhandled cases that should be fixed in due course.
|
||||
/// </summary>
|
||||
class CustomAttributeDecoder : ICustomAttributeTypeProvider<Type>
|
||||
{
|
||||
readonly Context cx;
|
||||
public CustomAttributeDecoder(Context cx) { this.cx = cx; }
|
||||
|
||||
public Type GetPrimitiveType(PrimitiveTypeCode typeCode) => cx.Populate(new PrimitiveType(cx, typeCode));
|
||||
|
||||
public Type GetSystemType() => throw new NotImplementedException();
|
||||
|
||||
public Type GetSZArrayType(Type elementType) =>
|
||||
cx.Populate(new ArrayType(cx, elementType));
|
||||
|
||||
public Type GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) =>
|
||||
(Type)cx.Create(handle);
|
||||
|
||||
public Type GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) =>
|
||||
(Type)cx.Create(handle);
|
||||
|
||||
public Type GetTypeFromSerializedName(string name) => throw new NotImplementedException();
|
||||
|
||||
public PrimitiveTypeCode GetUnderlyingEnumType(Type type) => throw new NotImplementedException();
|
||||
|
||||
public bool IsSystemType(Type type) => type is PrimitiveType; // ??
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// An event.
|
||||
/// </summary>
|
||||
interface IEvent : ILabelledEntity
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event entity.
|
||||
/// </summary>
|
||||
class Event : LabelledEntity, IEvent
|
||||
{
|
||||
readonly Type parent;
|
||||
readonly EventDefinition ed;
|
||||
static readonly Id suffix = CIL.Id.Create(";cil-event");
|
||||
|
||||
public Event(Context cx, Type parent, EventDefinitionHandle handle) : base(cx)
|
||||
{
|
||||
this.parent = parent;
|
||||
ed = cx.mdReader.GetEventDefinition(handle);
|
||||
ShortId = parent.ShortId + cx.Dot + cx.ShortName(ed.Name) + suffix;
|
||||
}
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
var signature = (Type)cx.CreateGeneric(parent, ed.Type);
|
||||
yield return signature;
|
||||
|
||||
yield return Tuples.cil_event(this, parent, cx.ShortName(ed.Name), signature);
|
||||
|
||||
var accessors = ed.GetAccessors();
|
||||
if (!accessors.Adder.IsNil)
|
||||
{
|
||||
var adder = (Method)cx.CreateGeneric(parent, accessors.Adder);
|
||||
yield return adder;
|
||||
yield return Tuples.cil_adder(this, adder);
|
||||
}
|
||||
|
||||
if (!accessors.Remover.IsNil)
|
||||
{
|
||||
var remover = (Method)cx.CreateGeneric(parent, accessors.Remover);
|
||||
yield return remover;
|
||||
yield return Tuples.cil_remover(this, remover);
|
||||
}
|
||||
|
||||
if (!accessors.Raiser.IsNil)
|
||||
{
|
||||
var raiser = (Method)cx.CreateGeneric(parent, accessors.Raiser);
|
||||
yield return raiser;
|
||||
yield return Tuples.cil_raiser(this, raiser);
|
||||
}
|
||||
|
||||
foreach (var c in Attribute.Populate(cx, this, ed.GetCustomAttributes()))
|
||||
yield return c;
|
||||
}
|
||||
}
|
||||
|
||||
public override Id IdSuffix => suffix;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Entities
|
||||
{
|
||||
interface IExceptionRegion : IExtractedEntity
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An exception region entity.
|
||||
/// </summary>
|
||||
class ExceptionRegion : UnlabelledEntity, IExceptionRegion
|
||||
{
|
||||
readonly GenericContext gc;
|
||||
readonly MethodImplementation method;
|
||||
readonly int index;
|
||||
readonly System.Reflection.Metadata.ExceptionRegion r;
|
||||
readonly Dictionary<int, IInstruction> jump_table;
|
||||
|
||||
public ExceptionRegion(GenericContext gc, MethodImplementation method, int index, System.Reflection.Metadata.ExceptionRegion r, Dictionary<int, IInstruction> jump_table) : base(gc.cx)
|
||||
{
|
||||
this.gc = gc;
|
||||
this.method = method;
|
||||
this.index = index;
|
||||
this.r = r;
|
||||
this.jump_table = jump_table;
|
||||
}
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
IInstruction try_start, try_end, handler_start;
|
||||
|
||||
if (!jump_table.TryGetValue(r.TryOffset, out try_start))
|
||||
throw new InternalError("Failed to retrieve handler");
|
||||
if (!jump_table.TryGetValue(r.TryOffset + r.TryLength, out try_end))
|
||||
throw new InternalError("Failed to retrieve handler");
|
||||
if (!jump_table.TryGetValue(r.HandlerOffset, out handler_start))
|
||||
throw new InternalError("Failed to retrieve handler");
|
||||
|
||||
|
||||
yield return Tuples.cil_handler(this, method, index, (int)r.Kind, try_start, try_end, handler_start);
|
||||
|
||||
if (r.FilterOffset != -1)
|
||||
{
|
||||
IInstruction filter_start;
|
||||
if (!jump_table.TryGetValue(r.FilterOffset, out filter_start))
|
||||
throw new InternalError("ExceptionRegion filter clause");
|
||||
|
||||
yield return Tuples.cil_handler_filter(this, filter_start);
|
||||
}
|
||||
|
||||
if (!r.CatchType.IsNil)
|
||||
{
|
||||
var catchType = (Type)cx.CreateGeneric(gc, r.CatchType);
|
||||
yield return catchType;
|
||||
yield return Tuples.cil_handler_type(this, catchType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// An entity represting a member.
|
||||
/// Used to type tuples correctly.
|
||||
/// </summary>
|
||||
interface IMember : ILabelledEntity
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An entity representing a field.
|
||||
/// </summary>
|
||||
interface IField : IMember
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An entity representing a field.
|
||||
/// </summary>
|
||||
abstract class Field : GenericContext, IField
|
||||
{
|
||||
protected Field(Context cx) : base(cx)
|
||||
{
|
||||
}
|
||||
|
||||
public bool NeedsPopulation { get { return true; } }
|
||||
|
||||
public Label Label { get; set; }
|
||||
|
||||
public IId Id => ShortId + IdSuffix;
|
||||
|
||||
public Id IdSuffix => fieldSuffix;
|
||||
|
||||
static readonly StringId fieldSuffix = new StringId(";cil-field");
|
||||
|
||||
public Id ShortId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public abstract Id Name { get; }
|
||||
|
||||
public abstract Type DeclaringType { get; }
|
||||
|
||||
public Location ReportingLocation => throw new NotImplementedException();
|
||||
|
||||
abstract public Type Type { get; }
|
||||
|
||||
public virtual IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return Tuples.cil_field(this, DeclaringType, Name.Value, Type);
|
||||
}
|
||||
}
|
||||
|
||||
public void Extract(Context cx)
|
||||
{
|
||||
cx.Populate(this);
|
||||
}
|
||||
|
||||
TrapStackBehaviour IEntity.TrapStackBehaviour => TrapStackBehaviour.NoLabel;
|
||||
}
|
||||
|
||||
sealed class DefinitionField : Field
|
||||
{
|
||||
readonly FieldDefinition fd;
|
||||
readonly GenericContext gc;
|
||||
|
||||
public DefinitionField(GenericContext gc, FieldDefinitionHandle handle) : base(gc.cx)
|
||||
{
|
||||
this.gc = gc;
|
||||
fd = cx.mdReader.GetFieldDefinition(handle);
|
||||
ShortId = DeclaringType.ShortId + cx.Dot + Name;
|
||||
}
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var c in base.Contents)
|
||||
yield return c;
|
||||
|
||||
if (fd.Attributes.HasFlag(FieldAttributes.Private))
|
||||
yield return Tuples.cil_private(this);
|
||||
|
||||
if (fd.Attributes.HasFlag(FieldAttributes.Public))
|
||||
yield return Tuples.cil_public(this);
|
||||
|
||||
if (fd.Attributes.HasFlag(FieldAttributes.Family))
|
||||
yield return Tuples.cil_protected(this);
|
||||
|
||||
if (fd.Attributes.HasFlag(FieldAttributes.Static))
|
||||
yield return Tuples.cil_static(this);
|
||||
|
||||
if (fd.Attributes.HasFlag(FieldAttributes.Assembly))
|
||||
yield return Tuples.cil_internal(this);
|
||||
|
||||
foreach (var c in Attribute.Populate(cx, this, fd.GetCustomAttributes()))
|
||||
yield return c;
|
||||
}
|
||||
}
|
||||
|
||||
public override Id Name => cx.GetId(fd.Name);
|
||||
|
||||
public override Type DeclaringType => (Type)cx.Create(fd.GetDeclaringType());
|
||||
|
||||
public override Type Type => fd.DecodeSignature(cx.TypeSignatureDecoder, DeclaringType);
|
||||
|
||||
public override IEnumerable<Type> TypeParameters => throw new NotImplementedException();
|
||||
|
||||
public override IEnumerable<Type> MethodParameters => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
sealed class MemberReferenceField : Field
|
||||
{
|
||||
readonly MemberReference mr;
|
||||
readonly GenericContext gc;
|
||||
readonly Type declType;
|
||||
|
||||
public MemberReferenceField(GenericContext gc, MemberReferenceHandle handle) : base(gc.cx)
|
||||
{
|
||||
this.gc = gc;
|
||||
mr = cx.mdReader.GetMemberReference(handle);
|
||||
declType = (Type)cx.CreateGeneric(gc, mr.Parent);
|
||||
ShortId = declType.ShortId + cx.Dot + Name;
|
||||
}
|
||||
|
||||
public override Id Name => cx.GetId(mr.Name);
|
||||
|
||||
public override Type DeclaringType => declType;
|
||||
|
||||
public override Type Type => mr.DecodeFieldSignature(cx.TypeSignatureDecoder, this);
|
||||
|
||||
public override IEnumerable<Type> TypeParameters => gc.TypeParameters.Concat(declType.TypeParameters);
|
||||
|
||||
public override IEnumerable<Type> MethodParameters => gc.MethodParameters.Concat(declType.MethodParameters);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Entities
|
||||
{
|
||||
interface IFileOrFolder : IEntity
|
||||
{
|
||||
}
|
||||
|
||||
interface IFile : IFileOrFolder
|
||||
{
|
||||
}
|
||||
|
||||
public class File : LabelledEntity, IFile
|
||||
{
|
||||
protected readonly string path;
|
||||
|
||||
public File(Context cx, string path) : base(cx)
|
||||
{
|
||||
this.path = path.Replace("\\", "/");
|
||||
ShortId = new StringId(path.Replace(":", "_"));
|
||||
}
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
var parent = cx.CreateFolder(System.IO.Path.GetDirectoryName(path));
|
||||
yield return parent;
|
||||
yield return Tuples.containerparent(parent, this);
|
||||
yield return Tuples.files(this, path, System.IO.Path.GetFileNameWithoutExtension(path), System.IO.Path.GetExtension(path).Substring(1));
|
||||
}
|
||||
}
|
||||
|
||||
public override Id IdSuffix => suffix;
|
||||
|
||||
static readonly Id suffix = new StringId(";sourcefile");
|
||||
}
|
||||
|
||||
public class PdbSourceFile : File
|
||||
{
|
||||
readonly PDB.ISourceFile file;
|
||||
|
||||
public PdbSourceFile(Context cx, PDB.ISourceFile file) : base(cx, file.Path)
|
||||
{
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var c in base.Contents)
|
||||
yield return c;
|
||||
|
||||
var text = file.Contents;
|
||||
|
||||
if (text == null)
|
||||
cx.cx.Extractor.Logger.Log(Util.Logging.Severity.Warning, string.Format("PDB source file {0} could not be found", path));
|
||||
else
|
||||
cx.cx.TrapWriter.Archive(path, text);
|
||||
|
||||
yield return Tuples.file_extraction_mode(this, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Entities
|
||||
{
|
||||
interface IFolder : IFileOrFolder
|
||||
{
|
||||
}
|
||||
|
||||
public class Folder : LabelledEntity, IFolder
|
||||
{
|
||||
readonly string path;
|
||||
|
||||
public Folder(Context cx, string path) : base(cx)
|
||||
{
|
||||
this.path = path;
|
||||
ShortId = new StringId(path.Replace("\\", "/").Replace(":", "_"));
|
||||
}
|
||||
|
||||
static readonly Id suffix = new StringId(";folder");
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
// On Posix, we could get a Windows directory of the form "C:"
|
||||
bool windowsDriveLetter = path.Length == 2 && char.IsLetter(path[0]) && path[1] == ':';
|
||||
|
||||
var parent = Path.GetDirectoryName(path);
|
||||
if (parent != null && !windowsDriveLetter)
|
||||
{
|
||||
var parentFolder = cx.CreateFolder(parent);
|
||||
yield return parentFolder;
|
||||
yield return Tuples.containerparent(parentFolder, this);
|
||||
}
|
||||
yield return Tuples.folders(this, path, Path.GetFileName(path));
|
||||
}
|
||||
}
|
||||
|
||||
public override Id IdSuffix => suffix;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,486 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// A CIL instruction.
|
||||
/// </summary>
|
||||
interface IInstruction : IExtractedEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the extraction products for branches.
|
||||
/// </summary>
|
||||
/// <param name="jump_table">The map from offset to instruction.</param>
|
||||
/// <returns>The extraction products.</returns>
|
||||
IEnumerable<IExtractionProduct> JumpContents(Dictionary<int, IInstruction> jump_table);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A CIL instruction.
|
||||
/// </summary>
|
||||
class Instruction : UnlabelledEntity, IInstruction
|
||||
{
|
||||
/// <summary>
|
||||
/// The additional data following the opcode, if any.
|
||||
/// </summary>
|
||||
public enum Payload
|
||||
{
|
||||
None, TypeTok, Field, Target8, Class,
|
||||
Method, Arg8, Local8, Target32, Int8,
|
||||
Int16, Int32, Int64, Float32, Float64,
|
||||
CallSiteDesc, Switch, String, Constructor, ValueType,
|
||||
Type, Arg16, Ignore8, Token, Local16, MethodRef
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For each Payload, how many additional bytes in the bytestream need to be read.
|
||||
/// </summary>
|
||||
internal static readonly int[] payloadSizes = {
|
||||
0, 4, 4, 1, 4,
|
||||
4, 1, 1, 4, 1,
|
||||
2, 4, 8, 4, 8,
|
||||
4, -1, 4, 4, 4,
|
||||
4, 2, 1, 4, 2, 4 };
|
||||
|
||||
// Maps opcodes to payloads for each instruction.
|
||||
public static readonly Dictionary<ILOpCode, Payload> opPayload = new Dictionary<ILOpCode, Payload>()
|
||||
{
|
||||
{ ILOpCode.Nop, Payload.None },
|
||||
{ ILOpCode.Break, Payload.None },
|
||||
{ ILOpCode.Ldarg_0, Payload.None },
|
||||
{ ILOpCode.Ldarg_1, Payload.None },
|
||||
{ ILOpCode.Ldarg_2, Payload.None },
|
||||
{ ILOpCode.Ldarg_3, Payload.None },
|
||||
{ ILOpCode.Ldloc_0, Payload.None },
|
||||
{ ILOpCode.Ldloc_1, Payload.None },
|
||||
{ ILOpCode.Ldloc_2, Payload.None },
|
||||
{ ILOpCode.Ldloc_3, Payload.None },
|
||||
{ ILOpCode.Stloc_0, Payload.None },
|
||||
{ ILOpCode.Stloc_1, Payload.None },
|
||||
{ ILOpCode.Stloc_2, Payload.None },
|
||||
{ ILOpCode.Stloc_3, Payload.None },
|
||||
{ ILOpCode.Ldarg_s, Payload.Arg8 },
|
||||
{ ILOpCode.Ldarga_s, Payload.Arg8 },
|
||||
{ ILOpCode.Starg_s, Payload.Arg8 },
|
||||
{ ILOpCode.Ldloc_s, Payload.Local8 },
|
||||
{ ILOpCode.Ldloca_s, Payload.Local8 },
|
||||
{ ILOpCode.Stloc_s, Payload.Local8 },
|
||||
{ ILOpCode.Ldnull, Payload.None },
|
||||
{ ILOpCode.Ldc_i4_m1, Payload.None },
|
||||
{ ILOpCode.Ldc_i4_0, Payload.None },
|
||||
{ ILOpCode.Ldc_i4_1, Payload.None },
|
||||
{ ILOpCode.Ldc_i4_2, Payload.None },
|
||||
{ ILOpCode.Ldc_i4_3, Payload.None },
|
||||
{ ILOpCode.Ldc_i4_4, Payload.None },
|
||||
{ ILOpCode.Ldc_i4_5, Payload.None },
|
||||
{ ILOpCode.Ldc_i4_6, Payload.None },
|
||||
{ ILOpCode.Ldc_i4_7, Payload.None },
|
||||
{ ILOpCode.Ldc_i4_8, Payload.None },
|
||||
{ ILOpCode.Ldc_i4_s, Payload.Int8 },
|
||||
{ ILOpCode.Ldc_i4, Payload.Int32 },
|
||||
{ ILOpCode.Ldc_i8, Payload.Int64 },
|
||||
{ ILOpCode.Ldc_r4, Payload.Float32 },
|
||||
{ ILOpCode.Ldc_r8, Payload.Float64 },
|
||||
{ ILOpCode.Dup, Payload.None },
|
||||
{ ILOpCode.Pop, Payload.None },
|
||||
{ ILOpCode.Jmp, Payload.Method },
|
||||
{ ILOpCode.Call, Payload.Method },
|
||||
{ ILOpCode.Calli, Payload.CallSiteDesc },
|
||||
{ ILOpCode.Ret, Payload.None },
|
||||
{ ILOpCode.Br_s, Payload.Target8 },
|
||||
{ ILOpCode.Brfalse_s, Payload.Target8 },
|
||||
{ ILOpCode.Brtrue_s, Payload.Target8 },
|
||||
{ ILOpCode.Beq_s, Payload.Target8 },
|
||||
{ ILOpCode.Bge_s, Payload.Target8 },
|
||||
{ ILOpCode.Bgt_s, Payload.Target8 },
|
||||
{ ILOpCode.Ble_s, Payload.Target8 },
|
||||
{ ILOpCode.Blt_s, Payload.Target8 },
|
||||
{ ILOpCode.Bne_un_s, Payload.Target8 },
|
||||
{ ILOpCode.Bge_un_s, Payload.Target8 },
|
||||
{ ILOpCode.Bgt_un_s, Payload.Target8 },
|
||||
{ ILOpCode.Ble_un_s, Payload.Target8 },
|
||||
{ ILOpCode.Blt_un_s, Payload.Target8 },
|
||||
{ ILOpCode.Br, Payload.Target32 },
|
||||
{ ILOpCode.Brfalse, Payload.Target32 },
|
||||
{ ILOpCode.Brtrue, Payload.Target32 },
|
||||
{ ILOpCode.Beq, Payload.Target32 },
|
||||
{ ILOpCode.Bge, Payload.Target32 },
|
||||
{ ILOpCode.Bgt, Payload.Target32 },
|
||||
{ ILOpCode.Ble, Payload.Target32 },
|
||||
{ ILOpCode.Blt, Payload.Target32 },
|
||||
{ ILOpCode.Bne_un, Payload.Target32 },
|
||||
{ ILOpCode.Bge_un, Payload.Target32 },
|
||||
{ ILOpCode.Bgt_un, Payload.Target32 },
|
||||
{ ILOpCode.Ble_un, Payload.Target32 },
|
||||
{ ILOpCode.Blt_un, Payload.Target32 },
|
||||
{ ILOpCode.Switch, Payload.Switch },
|
||||
{ ILOpCode.Ldind_i1, Payload.None },
|
||||
{ ILOpCode.Ldind_u1, Payload.None },
|
||||
{ ILOpCode.Ldind_i2, Payload.None },
|
||||
{ ILOpCode.Ldind_u2, Payload.None },
|
||||
{ ILOpCode.Ldind_i4, Payload.None },
|
||||
{ ILOpCode.Ldind_u4, Payload.None },
|
||||
{ ILOpCode.Ldind_i8, Payload.None },
|
||||
{ ILOpCode.Ldind_i, Payload.None },
|
||||
{ ILOpCode.Ldind_r4, Payload.None },
|
||||
{ ILOpCode.Ldind_r8, Payload.None },
|
||||
{ ILOpCode.Ldind_ref, Payload.None },
|
||||
{ ILOpCode.Stind_ref, Payload.None },
|
||||
{ ILOpCode.Stind_i1, Payload.None },
|
||||
{ ILOpCode.Stind_i2, Payload.None },
|
||||
{ ILOpCode.Stind_i4, Payload.None },
|
||||
{ ILOpCode.Stind_i8, Payload.None },
|
||||
{ ILOpCode.Stind_r4, Payload.None },
|
||||
{ ILOpCode.Stind_r8, Payload.None },
|
||||
{ ILOpCode.Add, Payload.None },
|
||||
{ ILOpCode.Sub, Payload.None },
|
||||
{ ILOpCode.Mul, Payload.None },
|
||||
{ ILOpCode.Div, Payload.None },
|
||||
{ ILOpCode.Div_un, Payload.None },
|
||||
{ ILOpCode.Rem, Payload.None },
|
||||
{ ILOpCode.Rem_un, Payload.None },
|
||||
{ ILOpCode.And, Payload.None },
|
||||
{ ILOpCode.Or, Payload.None },
|
||||
{ ILOpCode.Xor, Payload.None },
|
||||
{ ILOpCode.Shl, Payload.None },
|
||||
{ ILOpCode.Shr, Payload.None },
|
||||
{ ILOpCode.Shr_un, Payload.None },
|
||||
{ ILOpCode.Neg, Payload.None },
|
||||
{ ILOpCode.Not, Payload.None },
|
||||
{ ILOpCode.Conv_i1, Payload.None },
|
||||
{ ILOpCode.Conv_i2, Payload.None },
|
||||
{ ILOpCode.Conv_i4, Payload.None },
|
||||
{ ILOpCode.Conv_i8, Payload.None },
|
||||
{ ILOpCode.Conv_r4, Payload.None },
|
||||
{ ILOpCode.Conv_r8, Payload.None },
|
||||
{ ILOpCode.Conv_u4, Payload.None },
|
||||
{ ILOpCode.Conv_u8, Payload.None },
|
||||
{ ILOpCode.Callvirt, Payload.MethodRef },
|
||||
{ ILOpCode.Cpobj, Payload.TypeTok },
|
||||
{ ILOpCode.Ldobj, Payload.TypeTok },
|
||||
{ ILOpCode.Ldstr, Payload.String },
|
||||
{ ILOpCode.Newobj, Payload.Constructor },
|
||||
{ ILOpCode.Castclass, Payload.Class },
|
||||
{ ILOpCode.Isinst, Payload.Class },
|
||||
{ ILOpCode.Conv_r_un, Payload.None },
|
||||
{ ILOpCode.Unbox, Payload.ValueType },
|
||||
{ ILOpCode.Throw, Payload.None },
|
||||
{ ILOpCode.Ldfld, Payload.Field },
|
||||
{ ILOpCode.Ldflda, Payload.Field },
|
||||
{ ILOpCode.Stfld, Payload.Field },
|
||||
{ ILOpCode.Ldsfld, Payload.Field },
|
||||
{ ILOpCode.Ldsflda, Payload.Field },
|
||||
{ ILOpCode.Stsfld, Payload.Field },
|
||||
{ ILOpCode.Stobj, Payload.Field },
|
||||
{ ILOpCode.Conv_ovf_i1_un, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_i2_un, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_i4_un, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_i8_un, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_u1_un, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_u2_un, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_u4_un, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_u8_un, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_i_un, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_u_un, Payload.None },
|
||||
{ ILOpCode.Box, Payload.TypeTok },
|
||||
{ ILOpCode.Newarr, Payload.TypeTok },
|
||||
{ ILOpCode.Ldlen, Payload.None },
|
||||
{ ILOpCode.Ldelema, Payload.Class },
|
||||
{ ILOpCode.Ldelem_i1, Payload.None },
|
||||
{ ILOpCode.Ldelem_u1, Payload.None },
|
||||
{ ILOpCode.Ldelem_i2, Payload.None },
|
||||
{ ILOpCode.Ldelem_u2, Payload.None },
|
||||
{ ILOpCode.Ldelem_i4, Payload.None },
|
||||
{ ILOpCode.Ldelem_u4, Payload.None },
|
||||
{ ILOpCode.Ldelem_i8, Payload.None },
|
||||
{ ILOpCode.Ldelem_i, Payload.None },
|
||||
{ ILOpCode.Ldelem_r4, Payload.None },
|
||||
{ ILOpCode.Ldelem_r8, Payload.None },
|
||||
{ ILOpCode.Ldelem_ref, Payload.None },
|
||||
{ ILOpCode.Stelem_i, Payload.None },
|
||||
{ ILOpCode.Stelem_i1, Payload.None },
|
||||
{ ILOpCode.Stelem_i2, Payload.None },
|
||||
{ ILOpCode.Stelem_i4, Payload.None },
|
||||
{ ILOpCode.Stelem_i8, Payload.None },
|
||||
{ ILOpCode.Stelem_r4, Payload.None },
|
||||
{ ILOpCode.Stelem_r8, Payload.None },
|
||||
{ ILOpCode.Stelem_ref, Payload.None },
|
||||
{ ILOpCode.Ldelem, Payload.TypeTok },
|
||||
{ ILOpCode.Stelem, Payload.TypeTok },
|
||||
{ ILOpCode.Unbox_any, Payload.TypeTok },
|
||||
{ ILOpCode.Conv_ovf_i1, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_u1, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_i2, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_u2, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_i4, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_u4, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_i8, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_u8, Payload.None },
|
||||
{ ILOpCode.Refanyval, Payload.Type },
|
||||
{ ILOpCode.Ckfinite, Payload.None },
|
||||
{ ILOpCode.Mkrefany, Payload.Class },
|
||||
{ ILOpCode.Ldtoken, Payload.Token },
|
||||
{ ILOpCode.Conv_u2, Payload.None },
|
||||
{ ILOpCode.Conv_u1, Payload.None },
|
||||
{ ILOpCode.Conv_i, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_i, Payload.None },
|
||||
{ ILOpCode.Conv_ovf_u, Payload.None },
|
||||
{ ILOpCode.Add_ovf, Payload.None },
|
||||
{ ILOpCode.Add_ovf_un, Payload.None },
|
||||
{ ILOpCode.Mul_ovf, Payload.None },
|
||||
{ ILOpCode.Mul_ovf_un, Payload.None },
|
||||
{ ILOpCode.Sub_ovf, Payload.None },
|
||||
{ ILOpCode.Sub_ovf_un, Payload.None },
|
||||
{ ILOpCode.Endfinally, Payload.None },
|
||||
{ ILOpCode.Leave, Payload.Target32 },
|
||||
{ ILOpCode.Leave_s, Payload.Target8 },
|
||||
{ ILOpCode.Stind_i, Payload.None },
|
||||
{ ILOpCode.Conv_u, Payload.None },
|
||||
{ ILOpCode.Arglist, Payload.None },
|
||||
{ ILOpCode.Ceq, Payload.None },
|
||||
{ ILOpCode.Cgt, Payload.None },
|
||||
{ ILOpCode.Cgt_un, Payload.None },
|
||||
{ ILOpCode.Clt, Payload.None },
|
||||
{ ILOpCode.Clt_un, Payload.None },
|
||||
{ ILOpCode.Ldftn, Payload.Method },
|
||||
{ ILOpCode.Ldvirtftn, Payload.Method },
|
||||
{ ILOpCode.Ldarg, Payload.Arg16 },
|
||||
{ ILOpCode.Ldarga, Payload.Arg16 },
|
||||
{ ILOpCode.Starg, Payload.Arg16 },
|
||||
{ ILOpCode.Ldloc, Payload.Local16 },
|
||||
{ ILOpCode.Ldloca, Payload.Local16 },
|
||||
{ ILOpCode.Stloc, Payload.Local16 },
|
||||
{ ILOpCode.Localloc, Payload.None },
|
||||
{ ILOpCode.Endfilter, Payload.None },
|
||||
{ ILOpCode.Unaligned, Payload.Ignore8 },
|
||||
{ ILOpCode.Volatile, Payload.None },
|
||||
{ ILOpCode.Tail, Payload.None },
|
||||
{ ILOpCode.Initobj, Payload.TypeTok },
|
||||
{ ILOpCode.Constrained, Payload.Type },
|
||||
{ ILOpCode.Cpblk, Payload.None },
|
||||
{ ILOpCode.Initblk, Payload.None },
|
||||
{ ILOpCode.Rethrow, Payload.None },
|
||||
{ ILOpCode.Sizeof, Payload.TypeTok },
|
||||
{ ILOpCode.Refanytype, Payload.None },
|
||||
{ ILOpCode.Readonly, Payload.None }
|
||||
};
|
||||
|
||||
public readonly DefinitionMethod Method;
|
||||
public readonly ILOpCode OpCode;
|
||||
public readonly int Offset;
|
||||
public readonly int Index;
|
||||
readonly int PayloadValue;
|
||||
readonly uint UnsignedPayloadValue;
|
||||
|
||||
public Payload PayloadType
|
||||
{
|
||||
get
|
||||
{
|
||||
Payload result;
|
||||
if (!opPayload.TryGetValue(OpCode, out result))
|
||||
throw new InternalError("Unknown op code " + OpCode);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => Index + ": " + OpCode;
|
||||
|
||||
/// <summary>
|
||||
/// The number of bytes of this instruction,
|
||||
/// including the payload (if any).
|
||||
/// </summary>
|
||||
public int Width
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OpCode == ILOpCode.Switch) return 5 + 4 * PayloadValue;
|
||||
|
||||
return ((int)OpCode > 255 ? 2 : 1) + PayloadSize;
|
||||
}
|
||||
}
|
||||
|
||||
Label IEntity.Label
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
|
||||
readonly byte[] data;
|
||||
|
||||
int PayloadSize => payloadSizes[(int)PayloadType];
|
||||
|
||||
/// <summary>
|
||||
/// Reads the instruction from a byte stream.
|
||||
/// </summary>
|
||||
/// <param name="data">The byte stream.</param>
|
||||
/// <param name="offset">The offset of the instruction.</param>
|
||||
/// <param name="index">The index of this instruction in the callable.</param>
|
||||
public Instruction(Context cx, DefinitionMethod method, byte[] data, int offset, int index) : base(cx)
|
||||
{
|
||||
Method = method;
|
||||
Offset = offset;
|
||||
Index = index;
|
||||
this.data = data;
|
||||
int opcode = data[offset];
|
||||
++offset;
|
||||
|
||||
/*
|
||||
* An opcode is either 1 or 2 bytes, followed by an optional payload depending on the instruction.
|
||||
* Instructions where the first byte is 0xfe are 2-byte instructions.
|
||||
*/
|
||||
if (opcode == 0xfe)
|
||||
opcode = opcode << 8 | data[offset++];
|
||||
OpCode = (ILOpCode)opcode;
|
||||
|
||||
switch (PayloadSize)
|
||||
{
|
||||
case 0:
|
||||
PayloadValue = 0;
|
||||
break;
|
||||
case 1:
|
||||
PayloadValue = (sbyte)data[offset];
|
||||
UnsignedPayloadValue = data[offset];
|
||||
break;
|
||||
case 2:
|
||||
PayloadValue = BitConverter.ToInt16(data, offset);
|
||||
UnsignedPayloadValue = BitConverter.ToUInt16(data, offset);
|
||||
break;
|
||||
case -1: // Switch
|
||||
case 4:
|
||||
PayloadValue = BitConverter.ToInt32(data, offset);
|
||||
break;
|
||||
case 8: // Not handled here.
|
||||
break;
|
||||
default:
|
||||
throw new InternalError("Unhandled CIL instruction Payload");
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
int offset = Offset;
|
||||
|
||||
yield return Tuples.cil_instruction(this, (int)OpCode, Index, Method.Implementation);
|
||||
|
||||
switch (PayloadType)
|
||||
{
|
||||
case Payload.String:
|
||||
yield return Tuples.cil_value(this, cx.mdReader.GetUserString(MetadataTokens.UserStringHandle(PayloadValue)));
|
||||
break;
|
||||
case Payload.Float32:
|
||||
yield return Tuples.cil_value(this, BitConverter.ToSingle(data, offset).ToString());
|
||||
break;
|
||||
case Payload.Float64:
|
||||
yield return Tuples.cil_value(this, BitConverter.ToDouble(data, offset).ToString());
|
||||
break;
|
||||
case Payload.Int8:
|
||||
yield return Tuples.cil_value(this, data[offset].ToString());
|
||||
break;
|
||||
case Payload.Int16:
|
||||
yield return Tuples.cil_value(this, BitConverter.ToInt16(data, offset).ToString());
|
||||
break;
|
||||
case Payload.Int32:
|
||||
yield return Tuples.cil_value(this, BitConverter.ToInt32(data, offset).ToString());
|
||||
break;
|
||||
case Payload.Int64:
|
||||
yield return Tuples.cil_value(this, BitConverter.ToInt64(data, offset).ToString());
|
||||
break;
|
||||
case Payload.Constructor:
|
||||
case Payload.Method:
|
||||
case Payload.MethodRef:
|
||||
case Payload.Class:
|
||||
case Payload.TypeTok:
|
||||
case Payload.Token:
|
||||
case Payload.Type:
|
||||
case Payload.Field:
|
||||
case Payload.ValueType:
|
||||
// A generic EntityHandle.
|
||||
var handle = MetadataTokens.EntityHandle(PayloadValue);
|
||||
var target = cx.CreateGeneric(Method, handle);
|
||||
yield return target;
|
||||
if (target != null)
|
||||
{
|
||||
yield return Tuples.cil_access(this, target);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InternalError("Unable to create payload type {0} for opcode {1}", PayloadType, OpCode);
|
||||
}
|
||||
break;
|
||||
case Payload.Arg8:
|
||||
case Payload.Arg16:
|
||||
yield return Tuples.cil_access(this, Method.Parameters[(int)UnsignedPayloadValue]);
|
||||
break;
|
||||
case Payload.Local8:
|
||||
case Payload.Local16:
|
||||
yield return Tuples.cil_access(this, Method.LocalVariables[(int)UnsignedPayloadValue]);
|
||||
break;
|
||||
case Payload.None:
|
||||
case Payload.Target8:
|
||||
case Payload.Target32:
|
||||
case Payload.Switch:
|
||||
case Payload.Ignore8:
|
||||
case Payload.CallSiteDesc:
|
||||
// These are not handled here.
|
||||
// Some of these are handled by JumpContents().
|
||||
break;
|
||||
default:
|
||||
throw new InternalError("Unhandled payload type {0}", PayloadType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called to populate the jumps in each instruction.
|
||||
public IEnumerable<IExtractionProduct> JumpContents(Dictionary<int, IInstruction> jump_table)
|
||||
{
|
||||
int target;
|
||||
IInstruction inst;
|
||||
|
||||
switch (PayloadType)
|
||||
{
|
||||
case Payload.Target8:
|
||||
target = Offset + PayloadValue + 2;
|
||||
break;
|
||||
case Payload.Target32:
|
||||
target = Offset + PayloadValue + 5;
|
||||
break;
|
||||
case Payload.Switch:
|
||||
int end = Offset + Width;
|
||||
|
||||
int offset = Offset + 5;
|
||||
|
||||
for (int b = 0; b < PayloadValue; ++b, offset += 4)
|
||||
{
|
||||
target = BitConverter.ToInt32(data, offset) + end;
|
||||
if (!jump_table.TryGetValue(target, out inst))
|
||||
throw new InternalError("Invalid jump target");
|
||||
yield return Tuples.cil_switch(this, b, inst);
|
||||
}
|
||||
|
||||
yield break;
|
||||
default:
|
||||
// Not a jump
|
||||
yield break;
|
||||
}
|
||||
|
||||
|
||||
if (jump_table.TryGetValue(target, out inst))
|
||||
{
|
||||
yield return Tuples.cil_jump(this, inst);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sometimes instructions can jump outside the current method.
|
||||
// TODO: Find a solution to this.
|
||||
|
||||
// For now, just log the error
|
||||
cx.cx.Extractor.Message(new Message { message = "A CIL instruction jumps outside the current method", severity = Util.Logging.Severity.Warning });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Entities
|
||||
{
|
||||
interface ILocal : ILabelledEntity
|
||||
{
|
||||
}
|
||||
|
||||
class LocalVariable : LabelledEntity, ILocal
|
||||
{
|
||||
readonly MethodImplementation method;
|
||||
readonly int index;
|
||||
readonly Type type;
|
||||
|
||||
public LocalVariable(Context cx, MethodImplementation m, int i, Type t) : base(cx)
|
||||
{
|
||||
method = m;
|
||||
index = i;
|
||||
type = t;
|
||||
ShortId = CIL.Id.Create(method.Label) + underscore + index;
|
||||
}
|
||||
|
||||
static readonly Id underscore = CIL.Id.Create("_");
|
||||
static readonly Id suffix = CIL.Id.Create(";cil-local");
|
||||
public override Id IdSuffix => suffix;
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return type;
|
||||
yield return Tuples.cil_local_variable(this, method, index, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,515 @@
|
|||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Reflection.Metadata;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// A method entity.
|
||||
/// </summary>
|
||||
interface IMethod : IMember
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A method entity.
|
||||
/// </summary>
|
||||
abstract class Method : TypeContainer, IMethod
|
||||
{
|
||||
protected Method(GenericContext gc) : base(gc.cx)
|
||||
{
|
||||
this.gc = gc;
|
||||
}
|
||||
|
||||
public override IEnumerable<Type> TypeParameters => gc.TypeParameters.Concat(declaringType.TypeParameters);
|
||||
|
||||
public override IEnumerable<Type> MethodParameters => genericParams == null ? Enumerable.Empty<Type>() : genericParams;
|
||||
|
||||
public int GenericParameterCount => signature.GenericParameterCount;
|
||||
|
||||
public virtual Method SourceDeclaration => this;
|
||||
|
||||
public abstract Type DeclaringType { get; }
|
||||
public abstract string Name { get; }
|
||||
|
||||
public virtual IList<LocalVariable> LocalVariables => throw new NotImplementedException();
|
||||
public IList<Parameter> Parameters { get; private set; }
|
||||
|
||||
static readonly Id tick = CIL.Id.Create("`");
|
||||
static readonly Id space = CIL.Id.Create(" ");
|
||||
static readonly Id dot = CIL.Id.Create(".");
|
||||
static readonly Id open = CIL.Id.Create("(");
|
||||
static readonly Id close = CIL.Id.Create(")");
|
||||
|
||||
internal protected Id MakeMethodId(Type parent, Id methodName)
|
||||
{
|
||||
var id = signature.ReturnType.MakeId(gc) + space + parent.ShortId + dot + methodName;
|
||||
|
||||
if (signature.GenericParameterCount > 0)
|
||||
{
|
||||
id += tick + signature.GenericParameterCount;
|
||||
}
|
||||
|
||||
id += open + CIL.Id.CommaSeparatedList(signature.ParameterTypes.Select(p => p.MakeId(gc))) + close;
|
||||
return id;
|
||||
}
|
||||
|
||||
protected MethodTypeParameter[] genericParams;
|
||||
protected Type declaringType;
|
||||
protected GenericContext gc;
|
||||
protected MethodSignature<ITypeSignature> signature;
|
||||
protected Id name;
|
||||
|
||||
static readonly StringId methodSuffix = new StringId(";cil-method");
|
||||
|
||||
public override Id IdSuffix => methodSuffix;
|
||||
|
||||
protected void PopulateParameters(IEnumerable<Type> parameterTypes)
|
||||
{
|
||||
Parameters = MakeParameters(parameterTypes).ToArray();
|
||||
}
|
||||
|
||||
protected IEnumerable<IExtractionProduct> PopulateFlags
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsStatic)
|
||||
yield return Tuples.cil_static(this);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract bool IsStatic { get; }
|
||||
|
||||
private IEnumerable<Parameter> MakeParameters(IEnumerable<Type> parameterTypes)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (!IsStatic)
|
||||
{
|
||||
yield return cx.Populate(new Parameter(cx, this, i++, DeclaringType));
|
||||
}
|
||||
|
||||
foreach (var p in parameterTypes)
|
||||
yield return cx.Populate(new Parameter(cx, this, i++, p));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A method implementation entity.
|
||||
/// </summary>
|
||||
interface IMethodImplementation : IExtractedEntity
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A method implementation entity.
|
||||
/// In the database, the same method could in principle have multiple implementations.
|
||||
/// </summary>
|
||||
class MethodImplementation : UnlabelledEntity, IMethodImplementation
|
||||
{
|
||||
readonly Method m;
|
||||
|
||||
public MethodImplementation(Method m) : base(m.cx)
|
||||
{
|
||||
this.m = m;
|
||||
}
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return Tuples.cil_method_implementation(this, m, cx.assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A definition method - a method defined in the current assembly.
|
||||
/// </summary>
|
||||
class DefinitionMethod : Method, IMember
|
||||
{
|
||||
readonly MethodDefinition md;
|
||||
readonly PDB.IMethod methodDebugInformation;
|
||||
|
||||
LocalVariable[] locals;
|
||||
|
||||
public MethodImplementation Implementation { get; private set; }
|
||||
|
||||
public override IList<LocalVariable> LocalVariables => locals;
|
||||
|
||||
public DefinitionMethod(GenericContext gc, MethodDefinitionHandle handle) : base(gc)
|
||||
{
|
||||
md = cx.mdReader.GetMethodDefinition(handle);
|
||||
this.gc = gc;
|
||||
name = cx.GetId(md.Name);
|
||||
|
||||
declaringType = (Type)cx.CreateGeneric(this, md.GetDeclaringType());
|
||||
|
||||
signature = md.DecodeSignature(new SignatureDecoder(), this);
|
||||
ShortId = MakeMethodId(declaringType, name);
|
||||
|
||||
methodDebugInformation = cx.GetMethodDebugInformation(handle);
|
||||
}
|
||||
|
||||
public override bool IsStatic => !signature.Header.IsInstance;
|
||||
|
||||
public override Type DeclaringType => declaringType;
|
||||
|
||||
public override string Name => cx.ShortName(md.Name);
|
||||
|
||||
/// <summary>
|
||||
/// Holds if this method has bytecode.
|
||||
/// </summary>
|
||||
public bool HasBytecode => md.ImplAttributes == MethodImplAttributes.IL && md.RelativeVirtualAddress != 0;
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
if (md.GetGenericParameters().Any())
|
||||
{
|
||||
// We need to perform a 2-phase population because some type parameters can
|
||||
// depend on other type parameters (as a constraint).
|
||||
genericParams = new MethodTypeParameter[md.GetGenericParameters().Count];
|
||||
for (int i = 0; i < genericParams.Length; ++i)
|
||||
genericParams[i] = cx.Populate(new MethodTypeParameter(this, this, i));
|
||||
for (int i = 0; i < genericParams.Length; ++i)
|
||||
genericParams[i].PopulateHandle(this, md.GetGenericParameters()[i]);
|
||||
foreach (var p in genericParams)
|
||||
yield return p;
|
||||
}
|
||||
|
||||
var typeSignature = md.DecodeSignature(cx.TypeSignatureDecoder, this);
|
||||
|
||||
PopulateParameters(typeSignature.ParameterTypes);
|
||||
|
||||
foreach (var c in Parameters)
|
||||
yield return c;
|
||||
|
||||
foreach (var c in PopulateFlags)
|
||||
yield return c;
|
||||
|
||||
foreach (var p in md.GetParameters().Select(h => cx.mdReader.GetParameter(h)).Where(p => p.SequenceNumber > 0))
|
||||
{
|
||||
var pe = Parameters[IsStatic ? p.SequenceNumber - 1 : p.SequenceNumber];
|
||||
if (p.Attributes.HasFlag(ParameterAttributes.Out))
|
||||
yield return Tuples.cil_parameter_out(pe);
|
||||
if (p.Attributes.HasFlag(ParameterAttributes.In))
|
||||
yield return Tuples.cil_parameter_in(pe);
|
||||
Attribute.Populate(cx, pe, p.GetCustomAttributes());
|
||||
}
|
||||
|
||||
yield return Tuples.cil_method(this, Name, declaringType, typeSignature.ReturnType);
|
||||
yield return Tuples.cil_method_source_declaration(this, this);
|
||||
yield return Tuples.cil_method_location(this, cx.assembly);
|
||||
|
||||
if (HasBytecode)
|
||||
{
|
||||
Implementation = new MethodImplementation(this);
|
||||
yield return Implementation;
|
||||
|
||||
var body = cx.peReader.GetMethodBody(md.RelativeVirtualAddress);
|
||||
|
||||
if (!body.LocalSignature.IsNil)
|
||||
{
|
||||
var locals = cx.mdReader.GetStandaloneSignature(body.LocalSignature);
|
||||
var localVariableTypes = locals.DecodeLocalSignature(cx.TypeSignatureDecoder, this);
|
||||
|
||||
this.locals = new LocalVariable[localVariableTypes.Length];
|
||||
|
||||
for (int l = 0; l < this.locals.Length; ++l)
|
||||
{
|
||||
this.locals[l] = cx.Populate(new LocalVariable(cx, Implementation, l, localVariableTypes[l]));
|
||||
yield return this.locals[l];
|
||||
}
|
||||
}
|
||||
|
||||
var jump_table = new Dictionary<int, IInstruction>();
|
||||
|
||||
foreach (var c in Decode(body.GetILBytes(), jump_table))
|
||||
yield return c;
|
||||
|
||||
int filter_index = 0;
|
||||
foreach (var region in body.ExceptionRegions)
|
||||
{
|
||||
yield return new ExceptionRegion(this, Implementation, filter_index++, region, jump_table);
|
||||
}
|
||||
|
||||
yield return Tuples.cil_method_stack_size(Implementation, body.MaxStack);
|
||||
|
||||
if (methodDebugInformation != null)
|
||||
{
|
||||
var sourceLocation = cx.CreateSourceLocation(methodDebugInformation.Location);
|
||||
yield return sourceLocation;
|
||||
yield return Tuples.cil_method_location(this, sourceLocation);
|
||||
}
|
||||
}
|
||||
|
||||
// Flags
|
||||
|
||||
if (md.Attributes.HasFlag(MethodAttributes.Private))
|
||||
yield return Tuples.cil_private(this);
|
||||
|
||||
if (md.Attributes.HasFlag(MethodAttributes.Public))
|
||||
yield return Tuples.cil_public(this);
|
||||
|
||||
if (md.Attributes.HasFlag(MethodAttributes.Family))
|
||||
yield return Tuples.cil_protected(this);
|
||||
|
||||
if (md.Attributes.HasFlag(MethodAttributes.Final))
|
||||
yield return Tuples.cil_sealed(this);
|
||||
|
||||
if (md.Attributes.HasFlag(MethodAttributes.Virtual))
|
||||
yield return Tuples.cil_virtual(this);
|
||||
|
||||
if (md.Attributes.HasFlag(MethodAttributes.Abstract))
|
||||
yield return Tuples.cil_abstract(this);
|
||||
|
||||
if (md.Attributes.HasFlag(MethodAttributes.HasSecurity))
|
||||
yield return Tuples.cil_security(this);
|
||||
|
||||
if (md.Attributes.HasFlag(MethodAttributes.RequireSecObject))
|
||||
yield return Tuples.cil_requiresecobject(this);
|
||||
|
||||
if (md.Attributes.HasFlag(MethodAttributes.SpecialName))
|
||||
yield return Tuples.cil_specialname(this);
|
||||
|
||||
if (md.Attributes.HasFlag(MethodAttributes.NewSlot))
|
||||
yield return Tuples.cil_newslot(this);
|
||||
|
||||
// Populate attributes
|
||||
Attribute.Populate(cx, this, md.GetCustomAttributes());
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<IExtractionProduct> Decode(byte[] ilbytes, Dictionary<int, IInstruction> jump_table)
|
||||
{
|
||||
// Sequence points are stored in order of offset.
|
||||
// We use an enumerator to locate the correct sequence point for each instruction.
|
||||
// The sequence point gives the location of each instruction.
|
||||
// The location of an instruction is given by the sequence point *after* the
|
||||
// instruction.
|
||||
IEnumerator<PDB.SequencePoint> nextSequencePoint = null;
|
||||
PdbSourceLocation instructionLocation = null;
|
||||
|
||||
if (methodDebugInformation != null)
|
||||
{
|
||||
nextSequencePoint = methodDebugInformation.SequencePoints.GetEnumerator();
|
||||
if (nextSequencePoint.MoveNext())
|
||||
{
|
||||
instructionLocation = cx.CreateSourceLocation(nextSequencePoint.Current.Location);
|
||||
yield return instructionLocation;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextSequencePoint = null;
|
||||
}
|
||||
}
|
||||
|
||||
int child = 0;
|
||||
for (int offset = 0; offset < ilbytes.Length;)
|
||||
{
|
||||
var instruction = new Instruction(cx, this, ilbytes, offset, child++);
|
||||
yield return instruction;
|
||||
|
||||
if (nextSequencePoint != null && offset >= nextSequencePoint.Current.Offset)
|
||||
{
|
||||
instructionLocation = cx.CreateSourceLocation(nextSequencePoint.Current.Location);
|
||||
yield return instructionLocation;
|
||||
if (!nextSequencePoint.MoveNext())
|
||||
nextSequencePoint = null;
|
||||
}
|
||||
|
||||
if (instructionLocation != null)
|
||||
yield return Tuples.cil_instruction_location(instruction, instructionLocation);
|
||||
|
||||
jump_table.Add(instruction.Offset, instruction);
|
||||
offset += instruction.Width;
|
||||
}
|
||||
|
||||
foreach (var i in jump_table)
|
||||
{
|
||||
foreach (var t in i.Value.JumpContents(jump_table))
|
||||
yield return t;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display the instructions in the method in the debugger.
|
||||
/// This is only used for debugging, not in the code itself.
|
||||
/// </summary>
|
||||
public IEnumerable<Instruction> DebugInstructions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (md.ImplAttributes == MethodImplAttributes.IL && md.RelativeVirtualAddress != 0)
|
||||
{
|
||||
var body = cx.peReader.GetMethodBody(md.RelativeVirtualAddress);
|
||||
|
||||
var ilbytes = body.GetILBytes();
|
||||
|
||||
int child = 0;
|
||||
for (int offset = 0; offset < ilbytes.Length;)
|
||||
{
|
||||
Instruction decoded;
|
||||
try
|
||||
{
|
||||
decoded = new Instruction(cx, this, ilbytes, offset, child++);
|
||||
offset += decoded.Width;
|
||||
}
|
||||
catch
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
yield return decoded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a late-bound reference to a method.
|
||||
/// </summary>
|
||||
class MemberReferenceMethod : Method
|
||||
{
|
||||
readonly MemberReference mr;
|
||||
readonly Type declType;
|
||||
readonly GenericContext parent;
|
||||
readonly Method sourceDeclaration;
|
||||
|
||||
public MemberReferenceMethod(GenericContext gc, MemberReferenceHandle handle) : base(gc)
|
||||
{
|
||||
this.gc = gc;
|
||||
mr = cx.mdReader.GetMemberReference(handle);
|
||||
|
||||
signature = mr.DecodeMethodSignature(new SignatureDecoder(), gc);
|
||||
|
||||
parent = (GenericContext)cx.CreateGeneric(gc, mr.Parent);
|
||||
|
||||
var parentMethod = parent as Method;
|
||||
var nameLabel = cx.GetId(mr.Name);
|
||||
|
||||
declType = parentMethod == null ? parent as Type : parentMethod.DeclaringType;
|
||||
|
||||
ShortId = MakeMethodId(declType, nameLabel);
|
||||
|
||||
var typeSourceDeclaration = declType.SourceDeclaration;
|
||||
sourceDeclaration = typeSourceDeclaration == declType ? (Method)this : typeSourceDeclaration.LookupMethod(mr.Name, mr.Signature);
|
||||
}
|
||||
|
||||
public override Method SourceDeclaration => sourceDeclaration;
|
||||
|
||||
public override bool IsStatic => !signature.Header.IsInstance;
|
||||
|
||||
public override Type DeclaringType => declType;
|
||||
|
||||
public override string Name => cx.ShortName(mr.Name);
|
||||
|
||||
public override IEnumerable<Type> TypeParameters => parent.TypeParameters.Concat(gc.TypeParameters);
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
genericParams = new MethodTypeParameter[signature.GenericParameterCount];
|
||||
for (int p = 0; p < genericParams.Length; ++p)
|
||||
genericParams[p] = cx.Populate(new MethodTypeParameter(this, this, p));
|
||||
|
||||
foreach (var p in genericParams)
|
||||
yield return p;
|
||||
|
||||
var typeSignature = mr.DecodeMethodSignature(cx.TypeSignatureDecoder, this);
|
||||
|
||||
PopulateParameters(typeSignature.ParameterTypes);
|
||||
foreach (var p in Parameters) yield return p;
|
||||
|
||||
foreach (var f in PopulateFlags) yield return f;
|
||||
|
||||
yield return Tuples.cil_method(this, Name, DeclaringType, typeSignature.ReturnType);
|
||||
|
||||
if (SourceDeclaration != null)
|
||||
yield return Tuples.cil_method_source_declaration(this, SourceDeclaration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A constructed method.
|
||||
/// </summary>
|
||||
class MethodSpecificationMethod : Method
|
||||
{
|
||||
readonly MethodSpecification ms;
|
||||
readonly Method unboundMethod;
|
||||
readonly ImmutableArray<Type> typeParams;
|
||||
|
||||
public MethodSpecificationMethod(GenericContext gc, MethodSpecificationHandle handle) : base(gc)
|
||||
{
|
||||
ms = cx.mdReader.GetMethodSpecification(handle);
|
||||
|
||||
typeParams = ms.DecodeSignature(cx.TypeSignatureDecoder, gc);
|
||||
|
||||
unboundMethod = (Method)cx.CreateGeneric(gc, ms.Method);
|
||||
declaringType = unboundMethod.DeclaringType;
|
||||
|
||||
ShortId = unboundMethod.ShortId + openAngle + CIL.Id.CommaSeparatedList(typeParams.Select(p => p.ShortId)) + closeAngle;
|
||||
}
|
||||
|
||||
static readonly Id openAngle = CIL.Id.Create("<");
|
||||
static readonly Id closeAngle = CIL.Id.Create(">");
|
||||
|
||||
public override Method SourceDeclaration => unboundMethod;
|
||||
|
||||
public override Type DeclaringType => unboundMethod.DeclaringType;
|
||||
|
||||
public override string Name => unboundMethod.Name;
|
||||
|
||||
public override bool IsStatic => unboundMethod.IsStatic;
|
||||
|
||||
public override IEnumerable<Type> MethodParameters => typeParams;
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
MethodSignature<Type> constructedTypeSignature;
|
||||
switch (ms.Method.Kind)
|
||||
{
|
||||
case HandleKind.MemberReference:
|
||||
var mr = cx.mdReader.GetMemberReference((MemberReferenceHandle)ms.Method);
|
||||
constructedTypeSignature = mr.DecodeMethodSignature(cx.TypeSignatureDecoder, this);
|
||||
break;
|
||||
case HandleKind.MethodDefinition:
|
||||
var md = cx.mdReader.GetMethodDefinition((MethodDefinitionHandle)ms.Method);
|
||||
constructedTypeSignature = md.DecodeSignature(cx.TypeSignatureDecoder, this);
|
||||
break;
|
||||
default:
|
||||
throw new InternalError("Unexpected constructed method handle kind {0}", ms.Method.Kind);
|
||||
}
|
||||
|
||||
PopulateParameters(constructedTypeSignature.ParameterTypes);
|
||||
foreach (var p in Parameters)
|
||||
yield return p;
|
||||
|
||||
foreach (var f in PopulateFlags)
|
||||
yield return f;
|
||||
|
||||
yield return Tuples.cil_method(this, Name, DeclaringType, constructedTypeSignature.ReturnType);
|
||||
yield return Tuples.cil_method_source_declaration(this, SourceDeclaration);
|
||||
|
||||
if (typeParams.Count() != unboundMethod.GenericParameterCount)
|
||||
throw new InternalError("Method type parameter mismatch");
|
||||
|
||||
for (int p = 0; p < typeParams.Length; ++p)
|
||||
{
|
||||
yield return Tuples.cil_type_argument(this, p, typeParams[p]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Semmle.Extraction.Entities;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// A namespace.
|
||||
/// </summary>
|
||||
interface INamespace : ITypeContainer
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A namespace.
|
||||
/// </summary>
|
||||
public class Namespace : TypeContainer, INamespace
|
||||
{
|
||||
public Namespace ParentNamespace;
|
||||
public readonly StringId Name;
|
||||
|
||||
public bool IsGlobalNamespace => ParentNamespace == null;
|
||||
|
||||
static readonly Id suffix = CIL.Id.Create(";namespace");
|
||||
|
||||
public Id CreateId
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ParentNamespace != null && !ParentNamespace.IsGlobalNamespace)
|
||||
{
|
||||
return ParentNamespace.ShortId + cx.Dot + Name;
|
||||
}
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
public override Id IdSuffix => suffix;
|
||||
|
||||
public override IEnumerable<Type> TypeParameters => throw new NotImplementedException();
|
||||
|
||||
public override IEnumerable<Type> MethodParameters => throw new NotImplementedException();
|
||||
|
||||
static string parseNamespaceName(string fqn)
|
||||
{
|
||||
var i = fqn.LastIndexOf('.');
|
||||
return i == -1 ? fqn : fqn.Substring(i + 1);
|
||||
}
|
||||
|
||||
static Namespace createParentNamespace(Context cx, string fqn)
|
||||
{
|
||||
if (fqn == "") return null;
|
||||
var i = fqn.LastIndexOf('.');
|
||||
return i == -1 ? cx.GlobalNamespace : cx.Populate(new Namespace(cx, fqn.Substring(0, i)));
|
||||
}
|
||||
|
||||
public Namespace(Context cx, string fqn) : this(cx, cx.GetId(parseNamespaceName(fqn)), createParentNamespace(cx, fqn))
|
||||
{
|
||||
}
|
||||
|
||||
public Namespace(Context cx, StringId name, Namespace parent) : base(cx)
|
||||
{
|
||||
Name = name;
|
||||
ParentNamespace = parent;
|
||||
ShortId = CreateId;
|
||||
}
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return Tuples.namespaces(this, Name.Value);
|
||||
if (!IsGlobalNamespace)
|
||||
yield return Tuples.parent_namespace(this, ParentNamespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// A parameter entity.
|
||||
/// </summary>
|
||||
interface IParameter : ILabelledEntity
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A parameter entity.
|
||||
/// </summary>
|
||||
class Parameter : LabelledEntity, IParameter
|
||||
{
|
||||
readonly Method method;
|
||||
readonly int index;
|
||||
readonly Type type;
|
||||
|
||||
public Parameter(Context cx, Method m, int i, Type t) : base(cx)
|
||||
{
|
||||
method = m;
|
||||
index = i;
|
||||
type = t;
|
||||
ShortId = openCurly + method.Label.Value + closeCurly + index;
|
||||
}
|
||||
|
||||
static readonly Id parameterSuffix = CIL.Id.Create(";cil-parameter");
|
||||
static readonly Id openCurly = CIL.Id.Create("{#");
|
||||
static readonly Id closeCurly = CIL.Id.Create("}_");
|
||||
|
||||
public override Id IdSuffix => parameterSuffix;
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return Tuples.cil_parameter(this, method, index, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Linq;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// A property.
|
||||
/// </summary>
|
||||
interface IProperty : ILabelledEntity
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A property.
|
||||
/// </summary>
|
||||
class Property : LabelledEntity, IProperty
|
||||
{
|
||||
readonly Type type;
|
||||
readonly PropertyDefinition pd;
|
||||
static readonly Id suffix = CIL.Id.Create(";cil-property");
|
||||
|
||||
public Property(GenericContext gc, Type type, PropertyDefinitionHandle handle) : base(gc.cx)
|
||||
{
|
||||
pd = cx.mdReader.GetPropertyDefinition(handle);
|
||||
this.type = type;
|
||||
|
||||
var id = type.ShortId + gc.cx.Dot + cx.ShortName(pd.Name);
|
||||
var signature = pd.DecodeSignature(new SignatureDecoder(), gc);
|
||||
id += "(" + CIL.Id.CommaSeparatedList(signature.ParameterTypes.Select(p => p.MakeId(gc))) + ")";
|
||||
ShortId = id;
|
||||
}
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
var sig = pd.DecodeSignature(cx.TypeSignatureDecoder, type);
|
||||
|
||||
yield return Tuples.cil_property(this, type, cx.ShortName(pd.Name), sig.ReturnType);
|
||||
|
||||
var accessors = pd.GetAccessors();
|
||||
if (!accessors.Getter.IsNil)
|
||||
{
|
||||
var getter = (Method)cx.CreateGeneric(type, accessors.Getter);
|
||||
yield return getter;
|
||||
yield return Tuples.cil_getter(this, getter);
|
||||
}
|
||||
|
||||
if (!accessors.Setter.IsNil)
|
||||
{
|
||||
var setter = (Method)cx.CreateGeneric(type, accessors.Setter);
|
||||
yield return setter;
|
||||
yield return Tuples.cil_setter(this, setter);
|
||||
}
|
||||
|
||||
foreach (var c in Attribute.Populate(cx, this, pd.GetCustomAttributes()))
|
||||
yield return c;
|
||||
}
|
||||
}
|
||||
|
||||
public override Id IdSuffix => suffix;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using System.Collections.Generic;
|
||||
using Semmle.Extraction.PDB;
|
||||
|
||||
namespace Semmle.Extraction.CIL.Entities
|
||||
{
|
||||
public interface ISourceLocation : ILocation
|
||||
{
|
||||
}
|
||||
|
||||
public sealed class PdbSourceLocation : LabelledEntity, ISourceLocation
|
||||
{
|
||||
readonly Location location;
|
||||
readonly PdbSourceFile file;
|
||||
|
||||
public PdbSourceLocation(Context cx, PDB.Location location) : base(cx)
|
||||
{
|
||||
this.location = location;
|
||||
file = cx.CreateSourceFile(location.File);
|
||||
|
||||
ShortId = file.ShortId + separator + new IntId(location.StartLine) + separator + new IntId(location.StartColumn) + separator + new IntId(location.EndLine) + separator + new IntId(location.EndColumn);
|
||||
}
|
||||
|
||||
static readonly Id suffix = new StringId(";sourcelocation");
|
||||
static readonly Id separator = new StringId(",");
|
||||
|
||||
public override IEnumerable<IExtractionProduct> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return file;
|
||||
yield return Tuples.locations_default(this, file, location.StartLine, location.StartColumn, location.EndLine, location.EndColumn);
|
||||
}
|
||||
}
|
||||
|
||||
public override Id IdSuffix => suffix;
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,127 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Semmle.Extraction.CIL
|
||||
{
|
||||
/// <summary>
|
||||
/// Something that is extracted from an entity.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// The extraction algorithm proceeds as follows:
|
||||
/// - Construct entity
|
||||
/// - Call Extract()
|
||||
/// - ILabelledEntity check if already extracted
|
||||
/// - Enumerate Contents to produce more extraction products
|
||||
/// - Extract these until there is nothing left to extract
|
||||
/// </remarks>
|
||||
public interface IExtractionProduct
|
||||
{
|
||||
/// <summary>
|
||||
/// Perform further extraction/population of this item as necessary.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="cx">The extraction context.</param>
|
||||
void Extract(Context cx);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An entity which has been extracted.
|
||||
/// </summary>
|
||||
public interface IExtractedEntity : IEntity, IExtractionProduct
|
||||
{
|
||||
/// <summary>
|
||||
/// The contents of the entity.
|
||||
/// </summary>
|
||||
IEnumerable<IExtractionProduct> Contents { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An entity that has contents to extract. There is no need to populate
|
||||
/// a key as it's done in the contructor.
|
||||
/// </summary>
|
||||
public abstract class UnlabelledEntity : IExtractedEntity
|
||||
{
|
||||
public abstract IEnumerable<IExtractionProduct> Contents { get; }
|
||||
public Label Label { get; set; }
|
||||
|
||||
public Microsoft.CodeAnalysis.Location ReportingLocation => throw new NotImplementedException();
|
||||
|
||||
public virtual IId Id => FreshId.Instance;
|
||||
|
||||
public virtual void Extract(Context cx)
|
||||
{
|
||||
cx.Extract(this);
|
||||
}
|
||||
|
||||
public readonly Context cx;
|
||||
|
||||
protected UnlabelledEntity(Context cx)
|
||||
{
|
||||
this.cx = cx;
|
||||
cx.cx.AddFreshLabel(this);
|
||||
}
|
||||
|
||||
TrapStackBehaviour IEntity.TrapStackBehaviour => TrapStackBehaviour.NoLabel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An entity that needs to be populated during extraction.
|
||||
/// This assigns a key and optionally extracts its contents.
|
||||
/// </summary>
|
||||
public abstract class LabelledEntity : ILabelledEntity
|
||||
{
|
||||
public abstract IEnumerable<IExtractionProduct> Contents { get; }
|
||||
public Label Label { get; set; }
|
||||
public Microsoft.CodeAnalysis.Location ReportingLocation => throw new NotImplementedException();
|
||||
|
||||
public Id ShortId { get; set; }
|
||||
public abstract Id IdSuffix { get; }
|
||||
public IId Id => ShortId + IdSuffix;
|
||||
|
||||
public void Extract(Context cx)
|
||||
{
|
||||
cx.Populate(this);
|
||||
}
|
||||
|
||||
public readonly Context cx;
|
||||
|
||||
protected LabelledEntity(Context cx)
|
||||
{
|
||||
this.cx = cx;
|
||||
}
|
||||
|
||||
public override string ToString() => Id.ToString();
|
||||
|
||||
TrapStackBehaviour IEntity.TrapStackBehaviour => TrapStackBehaviour.NoLabel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An entity with a defined ID.
|
||||
/// </summary>
|
||||
public interface ILabelledEntity : IExtractedEntity
|
||||
{
|
||||
Id ShortId { get; set; }
|
||||
Id IdSuffix { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A tuple that is an extraction product.
|
||||
/// </summary>
|
||||
class Tuple : IExtractionProduct
|
||||
{
|
||||
readonly Extraction.Tuple tuple;
|
||||
|
||||
public Tuple(string name, params object[] args)
|
||||
{
|
||||
tuple = new Extraction.Tuple(name, args);
|
||||
}
|
||||
|
||||
public void Extract(Context cx)
|
||||
{
|
||||
cx.cx.Emit(tuple);
|
||||
}
|
||||
|
||||
public override string ToString() => tuple.ToString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
namespace Semmle.Extraction.CIL
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for creating and caching various entities.
|
||||
/// </summary>
|
||||
public partial class Context
|
||||
{
|
||||
readonly Dictionary<Id, (Label, Id)> ids = new Dictionary<Id, (Label, Id)>();
|
||||
|
||||
public T Populate<T>(T e) where T : ILabelledEntity
|
||||
{
|
||||
Id id = e.ShortId;
|
||||
|
||||
if (ids.TryGetValue(id, out var existing))
|
||||
{
|
||||
// It exists already
|
||||
e.Label = existing.Item1;
|
||||
e.ShortId = existing.Item2; // Reuse ID for efficiency
|
||||
}
|
||||
else
|
||||
{
|
||||
cx.DefineLabel(e);
|
||||
ids.Add(id, (e.Label, id));
|
||||
cx.PopulateLater(() =>
|
||||
{
|
||||
foreach (var c in e.Contents)
|
||||
c.Extract(this);
|
||||
});
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
public IExtractedEntity Create(Handle h)
|
||||
{
|
||||
var entity = CreateGeneric(defaultGenericContext, h);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an entity from a Handle in a GenericContext.
|
||||
/// The type of the returned entity depends on the type of the handle.
|
||||
/// The GenericContext is needed because some handles are generics which
|
||||
/// need to be expanded in terms of the current instantiation. If this sounds
|
||||
/// complex, you are right.
|
||||
///
|
||||
/// The pair (h,genericContext) is cached in case it is needed again.
|
||||
/// </summary>
|
||||
/// <param name="h">The handle of the entity.</param>
|
||||
/// <param name="genericContext">The generic context.</param>
|
||||
/// <returns></returns>
|
||||
public ILabelledEntity CreateGeneric(GenericContext genericContext, Handle h) => genericHandleFactory[genericContext, h];
|
||||
|
||||
readonly GenericContext defaultGenericContext;
|
||||
|
||||
ILabelledEntity CreateGenericHandle(GenericContext gc, Handle handle)
|
||||
{
|
||||
ILabelledEntity entity;
|
||||
switch (handle.Kind)
|
||||
{
|
||||
case HandleKind.MethodDefinition:
|
||||
entity = new Entities.DefinitionMethod(gc, (MethodDefinitionHandle)handle);
|
||||
break;
|
||||
case HandleKind.MemberReference:
|
||||
entity = Create(gc, (MemberReferenceHandle)handle);
|
||||
break;
|
||||
case HandleKind.MethodSpecification:
|
||||
entity = new Entities.MethodSpecificationMethod(gc, (MethodSpecificationHandle)handle);
|
||||
break;
|
||||
case HandleKind.FieldDefinition:
|
||||
entity = new Entities.DefinitionField(gc, (FieldDefinitionHandle)handle);
|
||||
break;
|
||||
case HandleKind.TypeReference:
|
||||
entity = new Entities.TypeReferenceType(this, (TypeReferenceHandle)handle);
|
||||
break;
|
||||
case HandleKind.TypeSpecification:
|
||||
entity = new Entities.TypeSpecificationType(gc, (TypeSpecificationHandle)handle);
|
||||
break;
|
||||
case HandleKind.TypeDefinition:
|
||||
entity = new Entities.TypeDefinitionType(this, (TypeDefinitionHandle)handle);
|
||||
break;
|
||||
default:
|
||||
throw new InternalError("Unhandled handle kind " + handle.Kind);
|
||||
}
|
||||
|
||||
Populate(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
ILabelledEntity Create(GenericContext gc, MemberReferenceHandle handle)
|
||||
{
|
||||
var mr = mdReader.GetMemberReference(handle);
|
||||
switch (mr.GetKind())
|
||||
{
|
||||
case MemberReferenceKind.Method:
|
||||
return new Entities.MemberReferenceMethod(gc, handle);
|
||||
case MemberReferenceKind.Field:
|
||||
return new Entities.MemberReferenceField(gc, handle);
|
||||
default:
|
||||
throw new InternalError("Unhandled member reference handle");
|
||||
}
|
||||
}
|
||||
|
||||
#region Strings
|
||||
readonly Dictionary<StringHandle, StringId> stringHandleIds = new Dictionary<StringHandle, StringId>();
|
||||
readonly Dictionary<string, StringId> stringIds = new Dictionary<string, StringId>();
|
||||
|
||||
/// <summary>
|
||||
/// Return an ID containing the given string.
|
||||
/// </summary>
|
||||
/// <param name="h">The string handle.</param>
|
||||
/// <returns>An ID.</returns>
|
||||
public StringId GetId(StringHandle h)
|
||||
{
|
||||
StringId result;
|
||||
if (!stringHandleIds.TryGetValue(h, out result))
|
||||
{
|
||||
result = new StringId(mdReader.GetString(h));
|
||||
stringHandleIds.Add(h, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public readonly StringId Dot = new StringId(".");
|
||||
|
||||
/// <summary>
|
||||
/// Gets an ID containing the given string.
|
||||
/// Caches existing IDs for more compact storage.
|
||||
/// </summary>
|
||||
/// <param name="str">The string.</param>
|
||||
/// <returns>An ID containing the string.</returns>
|
||||
public StringId GetId(string str)
|
||||
{
|
||||
StringId result;
|
||||
if (!stringIds.TryGetValue(str, out result))
|
||||
{
|
||||
result = new StringId(str);
|
||||
stringIds.Add(str, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Namespaces
|
||||
|
||||
readonly CachedFunction<StringHandle, Entities.Namespace> namespaceFactory;
|
||||
|
||||
public Entities.Namespace CreateNamespace(StringHandle fqn) => namespaceFactory[fqn];
|
||||
|
||||
readonly Lazy<Entities.Namespace> globalNamespace, systemNamespace;
|
||||
|
||||
/// <summary>
|
||||
/// The entity representing the global namespace.
|
||||
/// </summary>
|
||||
public Entities.Namespace GlobalNamespace => globalNamespace.Value;
|
||||
|
||||
/// <summary>
|
||||
/// The entity representing the System namespace.
|
||||
/// </summary>
|
||||
public Entities.Namespace SystemNamespace => systemNamespace.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a namespace from a fully-qualified name.
|
||||
/// </summary>
|
||||
/// <param name="fqn">The fully-qualified namespace name.</param>
|
||||
/// <returns>The namespace entity.</returns>
|
||||
Entities.Namespace CreateNamespace(string fqn) => Populate(new Entities.Namespace(this, fqn));
|
||||
|
||||
readonly CachedFunction<NamespaceDefinitionHandle, Entities.Namespace> namespaceDefinitionFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a namespace from a namespace handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the namespace.</param>
|
||||
/// <returns>The namespace entity.</returns>
|
||||
public Entities.Namespace Create(NamespaceDefinitionHandle handle) => namespaceDefinitionFactory[handle];
|
||||
|
||||
Entities.Namespace CreateNamespace(NamespaceDefinitionHandle handle)
|
||||
{
|
||||
if (handle.IsNil) return GlobalNamespace;
|
||||
NamespaceDefinition nd = mdReader.GetNamespaceDefinition(handle);
|
||||
return Populate(new Entities.Namespace(this, GetId(nd.Name), Create(nd.Parent)));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Locations
|
||||
readonly CachedFunction<PDB.ISourceFile, Entities.PdbSourceFile> sourceFiles;
|
||||
readonly CachedFunction<string, Entities.Folder> folders;
|
||||
readonly CachedFunction<PDB.Location, Entities.PdbSourceLocation> sourceLocations;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a source file entity from a PDB source file.
|
||||
/// </summary>
|
||||
/// <param name="file">The PDB source file.</param>
|
||||
/// <returns>A source file entity.</returns>
|
||||
public Entities.PdbSourceFile CreateSourceFile(PDB.ISourceFile file) => sourceFiles[file];
|
||||
|
||||
/// <summary>
|
||||
/// Creates a folder entitiy with the given path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the folder.</param>
|
||||
/// <returns>A folder entity.</returns>
|
||||
public Entities.Folder CreateFolder(string path) => folders[path];
|
||||
|
||||
/// <summary>
|
||||
/// Creates a source location.
|
||||
/// </summary>
|
||||
/// <param name="loc">The source location from PDB.</param>
|
||||
/// <returns>A source location entity.</returns>
|
||||
public Entities.PdbSourceLocation CreateSourceLocation(PDB.Location loc) => sourceLocations[loc];
|
||||
|
||||
#endregion
|
||||
|
||||
readonly CachedFunction<GenericContext, Handle, ILabelledEntity> genericHandleFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the short name of a member, without the preceding interface qualifier.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the name.</param>
|
||||
/// <returns>The short name.</returns>
|
||||
public string ShortName(StringHandle handle)
|
||||
{
|
||||
string str = mdReader.GetString(handle);
|
||||
if (str.EndsWith(".ctor")) return ".ctor";
|
||||
if (str.EndsWith(".cctor")) return ".cctor";
|
||||
var dot = str.LastIndexOf('.');
|
||||
return dot == -1 ? str : str.Substring(dot + 1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
namespace Semmle.Extraction.CIL
|
||||
{
|
||||
/// <summary>
|
||||
/// An ID fragment which is designed to be shared, reused
|
||||
/// and composed using the + operator.
|
||||
/// </summary>
|
||||
public abstract class Id : IId
|
||||
{
|
||||
public void AppendTo(ITrapBuilder tb)
|
||||
{
|
||||
tb.Append("@\"");
|
||||
BuildParts(tb);
|
||||
tb.Append("\"");
|
||||
}
|
||||
|
||||
public abstract void BuildParts(ITrapBuilder tb);
|
||||
|
||||
public static Id operator +(Id l1, Id l2) => Create(l1, l2);
|
||||
|
||||
public static Id operator +(Id l1, string l2) => Create(l1, Create(l2));
|
||||
|
||||
public static Id operator +(Id l1, int l2) => Create(l1, Create(l2));
|
||||
|
||||
public static Id operator +(string l1, Id l2) => Create(Create(l1), l2);
|
||||
|
||||
public static Id operator +(int l1, Id l2) => Create(Create(l1), l2);
|
||||
|
||||
public static Id Create(string s) => s == null ? null : new StringId(s);
|
||||
|
||||
public static Id Create(int i) => new IntId(i);
|
||||
|
||||
static readonly Id openCurly = Create("{#");
|
||||
static readonly Id closeCurly = Create("}");
|
||||
|
||||
public static Id Create(Label l) => openCurly + l.Value + closeCurly;
|
||||
|
||||
static readonly Id comma = Id.Create(",");
|
||||
|
||||
public static Id CommaSeparatedList(IEnumerable<Id> items)
|
||||
{
|
||||
Id result = null;
|
||||
bool first = true;
|
||||
foreach (var i in items)
|
||||
{
|
||||
if (first) first = false; else result += comma;
|
||||
result += i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Id Create(Id l1, Id l2)
|
||||
{
|
||||
return l1 == null ? l2 : l2 == null ? l1 : new ConsId(l1, l2);
|
||||
}
|
||||
|
||||
public abstract string Value { get; }
|
||||
|
||||
public override string ToString() => Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An ID concatenating two other IDs.
|
||||
/// </summary>
|
||||
public sealed class ConsId : Id
|
||||
{
|
||||
readonly Id left, right;
|
||||
readonly int hash;
|
||||
|
||||
public ConsId(Id l1, Id l2)
|
||||
{
|
||||
left = l1;
|
||||
right = l2;
|
||||
hash = unchecked(12 + 3 * (left.GetHashCode() + 51 * right.GetHashCode()));
|
||||
}
|
||||
|
||||
public override void BuildParts(ITrapBuilder tb)
|
||||
{
|
||||
left.BuildParts(tb);
|
||||
right.BuildParts(tb);
|
||||
}
|
||||
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
return other is ConsId && Equals((ConsId)other);
|
||||
}
|
||||
|
||||
public bool Equals(ConsId other)
|
||||
{
|
||||
return this == other ||
|
||||
(hash == other.hash && left.Equals(other.left) && right.Equals(other.right));
|
||||
}
|
||||
|
||||
public override int GetHashCode() => hash;
|
||||
|
||||
public override string Value => left.Value + right.Value;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A leaf ID storing a string.
|
||||
/// </summary>
|
||||
public sealed class StringId : Id
|
||||
{
|
||||
readonly string value;
|
||||
public override string Value => value;
|
||||
|
||||
public StringId(string s)
|
||||
{
|
||||
value = s;
|
||||
}
|
||||
|
||||
public override void BuildParts(ITrapBuilder tb)
|
||||
{
|
||||
tb.Append(value);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is StringId && ((StringId)obj).value == value;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => Value.GetHashCode() * 31 + 9;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A leaf ID storing an integer.
|
||||
/// </summary>
|
||||
public sealed class IntId : Id
|
||||
{
|
||||
readonly int value;
|
||||
public override string Value => value.ToString();
|
||||
|
||||
public IntId(int i)
|
||||
{
|
||||
value = i;
|
||||
}
|
||||
|
||||
public override void BuildParts(ITrapBuilder tb)
|
||||
{
|
||||
tb.Append(value);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is IntId && ((IntId)obj).value == value;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => unchecked(12 + value * 17);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Some predefined IDs.
|
||||
/// </summary>
|
||||
public static class IdUtils
|
||||
{
|
||||
public static StringId boolId = new StringId("Boolean");
|
||||
public static StringId byteId = new StringId("Byte");
|
||||
public static StringId charId = new StringId("Char");
|
||||
public static StringId doubleId = new StringId("Double");
|
||||
public static StringId shortId = new StringId("Int16");
|
||||
public static StringId intId = new StringId("Int32");
|
||||
public static StringId longId = new StringId("Int64");
|
||||
public static StringId intptrId = new StringId("IntPtr");
|
||||
public static StringId objectId = new StringId("Object");
|
||||
public static StringId sbyteId = new StringId("SByte");
|
||||
public static StringId floatId = new StringId("Single");
|
||||
public static StringId stringId = new StringId("String");
|
||||
public static StringId ushortId = new StringId("UInt16");
|
||||
public static StringId uintId = new StringId("UInt32");
|
||||
public static StringId ulongId = new StringId("UInt64");
|
||||
public static StringId uintptrId = new StringId("UIntPtr");
|
||||
public static StringId voidId = new StringId("Void");
|
||||
public static StringId typedReferenceId = new StringId("TypedReference");
|
||||
|
||||
public static StringId Id(this PrimitiveTypeCode typeCode)
|
||||
{
|
||||
switch (typeCode)
|
||||
{
|
||||
case PrimitiveTypeCode.Boolean: return boolId;
|
||||
case PrimitiveTypeCode.Byte: return byteId;
|
||||
case PrimitiveTypeCode.Char: return charId;
|
||||
case PrimitiveTypeCode.Double: return doubleId;
|
||||
case PrimitiveTypeCode.Int16: return shortId;
|
||||
case PrimitiveTypeCode.Int32: return intId;
|
||||
case PrimitiveTypeCode.Int64: return longId;
|
||||
case PrimitiveTypeCode.IntPtr: return intptrId;
|
||||
case PrimitiveTypeCode.Object: return objectId;
|
||||
case PrimitiveTypeCode.SByte: return sbyteId;
|
||||
case PrimitiveTypeCode.Single: return floatId;
|
||||
case PrimitiveTypeCode.String: return stringId;
|
||||
case PrimitiveTypeCode.UInt16: return ushortId;
|
||||
case PrimitiveTypeCode.UInt32: return uintId;
|
||||
case PrimitiveTypeCode.UInt64: return ulongId;
|
||||
case PrimitiveTypeCode.UIntPtr: return uintptrId;
|
||||
case PrimitiveTypeCode.Void: return voidId;
|
||||
case PrimitiveTypeCode.TypedReference: return typedReferenceId;
|
||||
default: throw new InternalError("Unhandled type code {0}", typeCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Reflection.PortableExecutable;
|
||||
|
||||
namespace Semmle.Extraction.PDB
|
||||
{
|
||||
/// <summary>
|
||||
/// A reader of PDB information using System.Reflection.Metadata.
|
||||
/// This is cross platform, and the future of PDB.
|
||||
///
|
||||
/// PDB information can be in a separate PDB file, or embedded in the DLL.
|
||||
/// </summary>
|
||||
class MetadataPdbReader : IPdb
|
||||
{
|
||||
class SourceFile : ISourceFile
|
||||
{
|
||||
public SourceFile(MetadataReader reader, DocumentHandle handle)
|
||||
{
|
||||
var doc = reader.GetDocument(handle);
|
||||
Path = reader.GetString(doc.Name);
|
||||
}
|
||||
|
||||
public string Path { get; private set; }
|
||||
|
||||
public string Contents => File.Exists(Path) ? File.ReadAllText(Path, System.Text.Encoding.Default) : null;
|
||||
}
|
||||
|
||||
// Turns out to be very important to keep the MetadataReaderProvider live
|
||||
// or the reader will crash.
|
||||
readonly MetadataReaderProvider provider;
|
||||
readonly MetadataReader reader;
|
||||
|
||||
public MetadataPdbReader(MetadataReaderProvider provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
reader = provider.GetMetadataReader();
|
||||
}
|
||||
|
||||
public IEnumerable<ISourceFile> SourceFiles => reader.Documents.Select(handle => new SourceFile(reader, handle));
|
||||
|
||||
public IMethod GetMethod(MethodDebugInformationHandle handle)
|
||||
{
|
||||
var debugInfo = reader.GetMethodDebugInformation(handle);
|
||||
|
||||
var sequencePoints = debugInfo.GetSequencePoints().
|
||||
Where(p => !p.Document.IsNil && !p.IsHidden).
|
||||
Select(p => new SequencePoint(p.Offset, new Location(new SourceFile(reader, p.Document), p.StartLine, p.StartColumn, p.EndLine, p.EndColumn))).
|
||||
Where(p => p.Location.File.Path != null).
|
||||
ToArray();
|
||||
|
||||
return sequencePoints.Any() ? new Method() { SequencePoints = sequencePoints } : null;
|
||||
}
|
||||
|
||||
public static MetadataPdbReader CreateFromAssembly(string assemblyPath, PEReader peReader)
|
||||
{
|
||||
foreach (var provider in peReader.
|
||||
ReadDebugDirectory().
|
||||
Where(d => d.Type == DebugDirectoryEntryType.EmbeddedPortablePdb).
|
||||
Select(dirEntry => peReader.ReadEmbeddedPortablePdbDebugDirectoryData(dirEntry)))
|
||||
{
|
||||
return new MetadataPdbReader(provider);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
MetadataReaderProvider provider;
|
||||
string pdbPath;
|
||||
if (peReader.TryOpenAssociatedPortablePdb(assemblyPath, s => new FileStream(s, FileMode.Open, FileAccess.Read, FileShare.Read), out provider, out pdbPath))
|
||||
{
|
||||
return new MetadataPdbReader(provider);
|
||||
}
|
||||
}
|
||||
|
||||
catch (BadImageFormatException)
|
||||
{
|
||||
// Something is wrong with the file.
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// The PDB file was not found.
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
provider.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.PortableExecutable;
|
||||
using Microsoft.DiaSymReader;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Reflection.Metadata;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Semmle.Extraction.PDB
|
||||
{
|
||||
/// <summary>
|
||||
/// A PDB reader using Microsoft.DiaSymReader.Native.
|
||||
/// This is an unmanaged Windows DLL, which therefore only works on Windows.
|
||||
/// </summary>
|
||||
class NativePdbReader : IPdb
|
||||
{
|
||||
sealed class Document : ISourceFile
|
||||
{
|
||||
readonly ISymUnmanagedDocument document;
|
||||
|
||||
public Document(ISymUnmanagedDocument doc)
|
||||
{
|
||||
document = doc;
|
||||
contents = new Lazy<string>(() =>
|
||||
{
|
||||
bool isEmbedded;
|
||||
if (document.HasEmbeddedSource(out isEmbedded) == 0 && isEmbedded)
|
||||
{
|
||||
var rawContents = document.GetEmbeddedSource().ToArray();
|
||||
return System.Text.Encoding.Default.GetString(rawContents);
|
||||
}
|
||||
else
|
||||
{
|
||||
return File.Exists(Path) ? File.ReadAllText(Path) : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var otherDoc = obj as Document;
|
||||
return otherDoc != null && Path.Equals(otherDoc.Path);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => Path.GetHashCode();
|
||||
|
||||
public string Path => document.GetName();
|
||||
|
||||
public override string ToString() => Path;
|
||||
|
||||
readonly Lazy<string> contents;
|
||||
|
||||
public string Contents => contents.Value;
|
||||
}
|
||||
|
||||
public IEnumerable<ISourceFile> SourceFiles => reader.GetDocuments().Select(d => new Document(d));
|
||||
|
||||
public IMethod GetMethod(MethodDebugInformationHandle h)
|
||||
{
|
||||
int methodToken = MetadataTokens.GetToken(h.ToDefinitionHandle());
|
||||
var method = reader.GetMethod(methodToken);
|
||||
if (method != null)
|
||||
{
|
||||
int count;
|
||||
if (method.GetSequencePointCount(out count) != 0 || count == 0)
|
||||
return null;
|
||||
|
||||
var s = method.GetSequencePoints().
|
||||
Where(sp => !sp.IsHidden).
|
||||
Select(sp => new SequencePoint(sp.Offset, new Location(new Document(sp.Document), sp.StartLine, sp.StartColumn, sp.EndLine, sp.EndColumn))).
|
||||
ToArray();
|
||||
|
||||
return s.Any() ? new Method { SequencePoints = s } : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
NativePdbReader(string path)
|
||||
{
|
||||
pdbStream = new FileStream(path, FileMode.Open);
|
||||
var metadataProvider = new MdProvider();
|
||||
reader = SymUnmanagedReaderFactory.CreateReader<ISymUnmanagedReader5>(pdbStream, metadataProvider);
|
||||
}
|
||||
|
||||
readonly ISymUnmanagedReader5 reader;
|
||||
readonly FileStream pdbStream;
|
||||
|
||||
public static NativePdbReader CreateFromAssembly(string assemblyPath, PEReader peReader)
|
||||
{
|
||||
// The Native PDB reader uses an unmanaged Windows DLL
|
||||
// so only works on Windows.
|
||||
if (!Semmle.Util.Win32.IsWindows())
|
||||
return null;
|
||||
|
||||
var debugDirectory = peReader.ReadDebugDirectory();
|
||||
|
||||
foreach (var path in debugDirectory.
|
||||
Where(d => d.Type == DebugDirectoryEntryType.CodeView).
|
||||
Select(peReader.ReadCodeViewDebugDirectoryData).
|
||||
Select(cv => cv.Path).
|
||||
Where(path => File.Exists(path)))
|
||||
{
|
||||
return new NativePdbReader(path);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
pdbStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is not used but is seemingly needed in order to use DiaSymReader.
|
||||
/// </summary>
|
||||
class MdProvider : ISymReaderMetadataProvider
|
||||
{
|
||||
public MdProvider()
|
||||
{
|
||||
}
|
||||
|
||||
public object GetMetadataImport() => null;
|
||||
|
||||
public unsafe bool TryGetStandaloneSignature(int standaloneSignatureToken, out byte* signature, out int length) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public bool TryGetTypeDefinitionInfo(int typeDefinitionToken, out string namespaceName, out string typeName, out TypeAttributes attributes, out int baseTypeToken) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public bool TryGetTypeDefinitionInfo(int typeDefinitionToken, out string namespaceName, out string typeName, out TypeAttributes attributes) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public bool TryGetTypeReferenceInfo(int typeReferenceToken, out string namespaceName, out string typeName, out int resolutionScopeToken) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public bool TryGetTypeReferenceInfo(int typeReferenceToken, out string namespaceName, out string typeName) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
using Microsoft.DiaSymReader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Reflection.PortableExecutable;
|
||||
|
||||
namespace Semmle.Extraction.PDB
|
||||
{
|
||||
/// <summary>
|
||||
/// A sequencepoint is a marker in the source code where you can put a breakpoint, and
|
||||
/// maps instructions to source code.
|
||||
/// </summary>
|
||||
public struct SequencePoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The byte-offset of the instruction.
|
||||
/// </summary>
|
||||
public readonly int Offset;
|
||||
|
||||
/// <summary>
|
||||
/// The source location of the instruction.
|
||||
/// </summary>
|
||||
public readonly Location Location;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0} = {1}", Offset, Location);
|
||||
}
|
||||
|
||||
public SequencePoint(int offset, Location location)
|
||||
{
|
||||
Offset = offset;
|
||||
Location = location;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A location in source code.
|
||||
/// </summary>
|
||||
public sealed class Location
|
||||
{
|
||||
/// <summary>
|
||||
/// The file containing the code.
|
||||
/// </summary>
|
||||
public readonly ISourceFile File;
|
||||
|
||||
/// <summary>
|
||||
/// The span of text within the text file.
|
||||
/// </summary>
|
||||
public readonly int StartLine, StartColumn, EndLine, EndColumn;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("({0},{1})-({2},{3})", StartLine, StartColumn, EndLine, EndColumn);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var otherLocation = obj as Location;
|
||||
|
||||
return otherLocation != null &&
|
||||
File.Equals(otherLocation.File) &&
|
||||
StartLine == otherLocation.StartLine &&
|
||||
StartColumn == otherLocation.StartColumn &&
|
||||
EndLine == otherLocation.EndLine &&
|
||||
EndColumn == otherLocation.EndColumn;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var h1 = StartLine + 37 * (StartColumn + 51 * (EndLine + 97 * EndColumn));
|
||||
return File.GetHashCode() + 17 * h1;
|
||||
}
|
||||
|
||||
public Location(ISourceFile file, int startLine, int startCol, int endLine, int endCol)
|
||||
{
|
||||
File = file;
|
||||
StartLine = startLine;
|
||||
StartColumn = startCol;
|
||||
EndLine = endLine;
|
||||
EndColumn = endCol;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IMethod
|
||||
{
|
||||
IEnumerable<SequencePoint> SequencePoints { get; }
|
||||
Location Location { get; }
|
||||
}
|
||||
|
||||
class Method : IMethod
|
||||
{
|
||||
public IEnumerable<SequencePoint> SequencePoints { get; set; }
|
||||
|
||||
public Location Location => SequencePoints.First().Location;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A source file reference in a PDB file.
|
||||
/// </summary>
|
||||
public interface ISourceFile
|
||||
{
|
||||
string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The contents of the file.
|
||||
/// This property is needed in case the contents
|
||||
/// of the file are embedded in the PDB instead of being on the filesystem.
|
||||
///
|
||||
/// null if the contents are unavailable.
|
||||
/// E.g. if the PDB file exists but the corresponding source files are missing.
|
||||
/// </summary>
|
||||
string Contents { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for reading PDB files.
|
||||
/// This is needed because there are different libraries for dealing with
|
||||
/// different types of PDB file, even though they share the same file extension.
|
||||
/// </summary>
|
||||
public interface IPdb : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all source files in this PDB.
|
||||
/// </summary>
|
||||
IEnumerable<ISourceFile> SourceFiles { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Look up a method from a given handle.
|
||||
/// </summary>
|
||||
/// <param name="methodHandle">The handle to query.</param>
|
||||
/// <returns>The method information, or null if the method does not have debug information.</returns>
|
||||
IMethod GetMethod(MethodDebugInformationHandle methodHandle);
|
||||
}
|
||||
|
||||
class PdbReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the PDB information associated with an assembly.
|
||||
/// </summary>
|
||||
/// <param name="assemblyPath">The path to the assembly.</param>
|
||||
/// <param name="peReader">The PE reader for the assembky.</param>
|
||||
/// <returns>A PdbReader, or null if no PDB information is available.</returns>
|
||||
public static IPdb Create(string assemblyPath, PEReader peReader)
|
||||
{
|
||||
return (IPdb)MetadataPdbReader.CreateFromAssembly(assemblyPath, peReader) ??
|
||||
NativePdbReader.CreateFromAssembly(assemblyPath, peReader);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Semmle.Extraction.CIL")]
|
||||
[assembly: AssemblyDescription("Semme CIL extractor.")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Semmle.Extraction.CIL")]
|
||||
[assembly: AssemblyCopyright("Copyright © Semmle 2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("a23d9ec2-8aae-43da-97cb-579f640b89cd")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -0,0 +1,26 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<AssemblyName>Semmle.Extraction.CIL</AssemblyName>
|
||||
<RootNamespace>Semmle.Extraction.CIL</RootNamespace>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Semmle.Extraction\Semmle.Extraction.csproj" />
|
||||
<ProjectReference Include="..\Semmle.Util\Semmle.Util.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.DiaSymReader" Version="1.3.0" />
|
||||
<PackageReference Include="Microsoft.DiaSymReader.Native" Version="1.7.0" />
|
||||
<PackageReference Include="Microsoft.DiaSymReader.PortablePdb" Version="1.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,209 @@
|
|||
using Semmle.Extraction.CIL.Entities;
|
||||
|
||||
namespace Semmle.Extraction.CIL
|
||||
{
|
||||
|
||||
internal static class Tuples
|
||||
{
|
||||
internal static Tuple assemblies(Assembly assembly, File file, string identifier, string name, string version) =>
|
||||
new Tuple("assemblies", assembly, file, identifier, name, version);
|
||||
|
||||
internal static Tuple cil_abstract(IMember method) =>
|
||||
new Tuple("cil_abstract", method);
|
||||
|
||||
internal static Tuple cil_adder(IEvent member, IMethod method) =>
|
||||
new Tuple("cil_adder", member, method);
|
||||
|
||||
internal static Tuple cil_access(IInstruction i, IEntity m) =>
|
||||
new Tuple("cil_access", i, m);
|
||||
|
||||
internal static Tuple cil_attribute(IAttribute attribute, IEntity @object, IMethod constructor) =>
|
||||
new Tuple("cil_attribute", attribute, @object, constructor);
|
||||
|
||||
internal static Tuple cil_attribute_named_argument(IAttribute attribute, string name, string value) =>
|
||||
new Tuple("cil_attribute_named_argument", attribute, name, value);
|
||||
|
||||
internal static Tuple cil_attribute_positional_argument(IAttribute attribute, int index, string value) =>
|
||||
new Tuple("cil_attribute_positional_argument", attribute, index, value);
|
||||
|
||||
internal static Tuple cil_array_type(IArrayType array, IType element, int rank) =>
|
||||
new Tuple("cil_array_type", array, element, rank);
|
||||
|
||||
internal static Tuple cil_base_class(IType t, IType @base) =>
|
||||
new Tuple("cil_base_class", t, @base);
|
||||
|
||||
internal static Tuple cil_base_interface(IType t, IType @base) =>
|
||||
new Tuple("cil_base_interface", t, @base);
|
||||
|
||||
internal static Tuple cil_class(IMember method) =>
|
||||
new Tuple("cil_class", method);
|
||||
|
||||
internal static Tuple cil_event(IEvent e, IType parent, string name, IType type) =>
|
||||
new Tuple("cil_event", e, parent, name, type);
|
||||
|
||||
internal static Tuple cil_field(IField field, IType parent, string name, IType fieldType) =>
|
||||
new Tuple("cil_field", field, parent, name, fieldType);
|
||||
|
||||
internal static Tuple cil_getter(IProperty member, IMethod method) =>
|
||||
new Tuple("cil_getter", member, method);
|
||||
|
||||
internal static Tuple cil_handler(IExceptionRegion region, IMethodImplementation method, int index, int kind,
|
||||
IInstruction region_start,
|
||||
IInstruction region_end,
|
||||
IInstruction handler_start) =>
|
||||
new Tuple("cil_handler", region, method, index, kind, region_start, region_end, handler_start);
|
||||
|
||||
internal static Tuple cil_handler_filter(IExceptionRegion region, IInstruction filter_start) =>
|
||||
new Tuple("cil_handler_filter", region, filter_start);
|
||||
|
||||
internal static Tuple cil_handler_type(IExceptionRegion region, Type t) =>
|
||||
new Tuple("cil_handler_type", region, t);
|
||||
|
||||
internal static Tuple cil_implements(IMethod derived, IMethod declaration) =>
|
||||
new Tuple("cil_implements", derived, declaration);
|
||||
|
||||
internal static Tuple cil_instruction(IInstruction instruction, int opcode, int index, IMethodImplementation parent) =>
|
||||
new Tuple("cil_instruction", instruction, opcode, index, parent);
|
||||
|
||||
internal static Tuple cil_instruction_location(Instruction i, ILocation loc) =>
|
||||
new Tuple("cil_instruction_location", i, loc);
|
||||
|
||||
internal static Tuple cil_interface(IMember method) =>
|
||||
new Tuple("cil_interface", method);
|
||||
|
||||
internal static Tuple cil_internal(IMember modifiable) =>
|
||||
new Tuple("cil_internal", modifiable);
|
||||
|
||||
internal static Tuple cil_jump(IInstruction from, IInstruction to) =>
|
||||
new Tuple("cil_jump", from, to);
|
||||
|
||||
internal static Tuple cil_local_variable(ILocal l, IMethodImplementation m, int i, Type t) =>
|
||||
new Tuple("cil_local_variable", l, m, i, t);
|
||||
|
||||
internal static Tuple cil_method(IMethod method, string name, IType declType, IType returnType) =>
|
||||
new Tuple("cil_method", method, name, declType, returnType);
|
||||
|
||||
internal static Tuple cil_method_implementation(IMethodImplementation impl, IMethod method, IAssembly assembly) =>
|
||||
new Tuple("cil_method_implementation", impl, method, assembly);
|
||||
|
||||
internal static Tuple cil_method_location(IMethod m, ILocation a) =>
|
||||
new Tuple("cil_method_location", m, a);
|
||||
|
||||
internal static Tuple cil_method_source_declaration(IMethod method, IMethod sourceDecl) =>
|
||||
new Tuple("cil_method_source_declaration", method, sourceDecl);
|
||||
|
||||
internal static Tuple cil_method_stack_size(IMethodImplementation method, int stackSize) =>
|
||||
new Tuple("cil_method_stack_size", method, stackSize);
|
||||
|
||||
internal static Tuple cil_newslot(IMethod method) =>
|
||||
new Tuple("cil_newslot", method);
|
||||
|
||||
internal static Tuple cil_parameter(IParameter p, IMethod m, int i, IType t) =>
|
||||
new Tuple("cil_parameter", p, m, i, t);
|
||||
|
||||
internal static Tuple cil_parameter_in(IParameter p) =>
|
||||
new Tuple("cil_parameter_in", p);
|
||||
|
||||
internal static Tuple cil_parameter_out(IParameter p) =>
|
||||
new Tuple("cil_parameter_out", p);
|
||||
|
||||
internal static Tuple cil_pointer_type(IPointerType t, IType pointee) =>
|
||||
new Tuple("cil_pointer_type", t, pointee);
|
||||
|
||||
internal static Tuple cil_private(IMember modifiable) =>
|
||||
new Tuple("cil_private", modifiable);
|
||||
|
||||
internal static Tuple cil_protected(IMember modifiable) =>
|
||||
new Tuple("cil_protected", modifiable);
|
||||
|
||||
internal static Tuple cil_property(IProperty p, IType parent, string name, IType propType) =>
|
||||
new Tuple("cil_property", p, parent, name, propType);
|
||||
|
||||
internal static Tuple cil_public(IMember modifiable) =>
|
||||
new Tuple("cil_public", modifiable);
|
||||
|
||||
internal static Tuple cil_raiser(IEvent member, IMethod method) =>
|
||||
new Tuple("cil_raiser", member, method);
|
||||
|
||||
internal static Tuple cil_requiresecobject(IMethod method) =>
|
||||
new Tuple("cil_requiresecobject", method);
|
||||
|
||||
internal static Tuple cil_remover(IEvent member, IMethod method) =>
|
||||
new Tuple("cil_remover", member, method);
|
||||
|
||||
internal static Tuple cil_sealed(IMember modifiable) =>
|
||||
new Tuple("cil_sealed", modifiable);
|
||||
|
||||
internal static Tuple cil_security(IMember method) =>
|
||||
new Tuple("cil_security", method);
|
||||
|
||||
internal static Tuple cil_setter(IProperty member, IMethod method) =>
|
||||
new Tuple("cil_setter", member, method);
|
||||
|
||||
internal static Tuple cil_specialname(IMethod method) =>
|
||||
new Tuple("cil_specialname", method);
|
||||
|
||||
internal static Tuple cil_static(IMember modifiable) =>
|
||||
new Tuple("cil_static", modifiable);
|
||||
|
||||
internal static Tuple cil_switch(IInstruction from, int index, IInstruction to) =>
|
||||
new Tuple("cil_switch", from, index, to);
|
||||
|
||||
internal static Tuple cil_type(IType t, string name, CilTypeKind kind, ITypeContainer parent, IType sourceDecl) =>
|
||||
new Tuple("cil_type", t, name, (int)kind, parent, sourceDecl);
|
||||
|
||||
internal static Tuple cil_type_argument(ITypeContainer constructedTypeOrMethod, int index, IType argument) =>
|
||||
new Tuple("cil_type_argument", constructedTypeOrMethod, index, argument);
|
||||
|
||||
internal static Tuple cil_type_location(IType t, IAssembly a) =>
|
||||
new Tuple("cil_type_location", t, a);
|
||||
|
||||
internal static Tuple cil_type_parameter(ITypeContainer unboundTypeOrMethod, int index, ITypeParameter parameter) =>
|
||||
new Tuple("cil_type_parameter", unboundTypeOrMethod, index, parameter);
|
||||
|
||||
internal static Tuple cil_typeparam_covariant(ITypeParameter p) =>
|
||||
new Tuple("cil_typeparam_covariant", p);
|
||||
|
||||
internal static Tuple cil_typeparam_contravariant(ITypeParameter p) =>
|
||||
new Tuple("cil_typeparam_contravariant", p);
|
||||
|
||||
internal static Tuple cil_typeparam_class(ITypeParameter p) =>
|
||||
new Tuple("cil_typeparam_class", p);
|
||||
|
||||
internal static Tuple cil_typeparam_constraint(ITypeParameter p, IType constraint) =>
|
||||
new Tuple("cil_typeparam_constraint", p, constraint);
|
||||
|
||||
internal static Tuple cil_typeparam_new(ITypeParameter p) =>
|
||||
new Tuple("cil_typeparam_new", p);
|
||||
|
||||
internal static Tuple cil_typeparam_struct(ITypeParameter p) =>
|
||||
new Tuple("cil_typeparam_struct", p);
|
||||
|
||||
internal static Tuple cil_value(IInstruction i, string value) =>
|
||||
new Tuple("cil_value", i, value);
|
||||
|
||||
internal static Tuple cil_virtual(IMethod method) =>
|
||||
new Tuple("cil_virtual", method);
|
||||
|
||||
internal static Tuple containerparent(IFolder parent, IFileOrFolder child) =>
|
||||
new Tuple("containerparent", parent, child);
|
||||
|
||||
internal static Tuple files(IFile file, string fullName, string name, string extension) =>
|
||||
new Tuple("files", file, fullName, name, extension, 0);
|
||||
|
||||
internal static Tuple file_extraction_mode(IFile file, int mode) =>
|
||||
new Tuple("file_extraction_mode", file, mode);
|
||||
|
||||
internal static Tuple folders(IFolder folder, string path, string name) =>
|
||||
new Tuple("folders", folder, path, name);
|
||||
|
||||
internal static Tuple locations_default(ISourceLocation label, IFile file, int startLine, int startCol, int endLine, int endCol) =>
|
||||
new Tuple("locations_default", label, file, startLine, startCol, endLine, endCol);
|
||||
|
||||
internal static Tuple namespaces(INamespace ns, string name) =>
|
||||
new Tuple("namespaces", ns, name);
|
||||
|
||||
internal static Tuple parent_namespace(ITypeContainer child, INamespace parent) =>
|
||||
new Tuple("parent_namespace", child, parent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
namespace Semmle.Extraction.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// A command-line driver for the extractor.
|
||||
/// </summary>
|
||||
public class Driver
|
||||
{
|
||||
static int Main(string[] args)
|
||||
{
|
||||
return (int)Extractor.Run(args);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Semmle.Extraction.CSharp.Driver")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Semmle.Extraction.CSharp.Driver")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("c02a2b0e-8884-4b82-8275-ea282403a775")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<AssemblyName>Semmle.Extraction.CSharp.Driver</AssemblyName>
|
||||
<RootNamespace>Semmle.Extraction.CSharp.Driver</RootNamespace>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Semmle.Extraction.CSharp\Semmle.Extraction.CSharp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,184 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System;
|
||||
|
||||
namespace Semmle.BuildAnalyser
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the set of assemblies.
|
||||
/// Searches for assembly DLLs, indexes them and provides
|
||||
/// a lookup facility from assembly ID to filename.
|
||||
/// </summary>
|
||||
class AssemblyCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Locate all reference files and index them.
|
||||
/// </summary>
|
||||
/// <param name="dirs">Directories to search.</param>
|
||||
/// <param name="progress">Callback for progress.</param>
|
||||
public AssemblyCache(IEnumerable<string> dirs, IProgressMonitor progress)
|
||||
{
|
||||
foreach (var dir in dirs)
|
||||
{
|
||||
progress.FindingFiles(dir);
|
||||
AddReferenceDirectory(dir);
|
||||
}
|
||||
IndexReferences();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all assemblies nested within a directory
|
||||
/// and adds them to its index.
|
||||
/// (Indexing is performed at a later stage by IndexReferences()).
|
||||
/// </summary>
|
||||
/// <param name="dir">The directory to index.</param>
|
||||
/// <returns>The number of DLLs within this directory.</returns>
|
||||
int AddReferenceDirectory(string dir)
|
||||
{
|
||||
int count = 0;
|
||||
foreach (var dll in new DirectoryInfo(dir).EnumerateFiles("*.dll", SearchOption.AllDirectories))
|
||||
{
|
||||
dlls.Add(dll.FullName);
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indexes all DLLs we have located.
|
||||
/// Because this is a potentially time-consuming operation, it is put into a separate stage.
|
||||
/// </summary>
|
||||
void IndexReferences()
|
||||
{
|
||||
// Read all of the files
|
||||
foreach (var filename in dlls)
|
||||
{
|
||||
var info = AssemblyInfo.ReadFromFile(filename);
|
||||
|
||||
if (info.Valid)
|
||||
{
|
||||
assemblyInfo[filename] = info;
|
||||
}
|
||||
else
|
||||
{
|
||||
failedDlls.Add(filename);
|
||||
}
|
||||
}
|
||||
|
||||
// Index "assemblyInfo" by version string
|
||||
// The OrderBy is used to ensure that we by default select the highest version number.
|
||||
foreach (var info in assemblyInfo.Values.OrderBy(info => info.Id))
|
||||
{
|
||||
foreach (var index in info.IndexStrings)
|
||||
references[index] = info;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of DLLs which are assemblies.
|
||||
/// </summary>
|
||||
public int AssemblyCount => assemblyInfo.Count;
|
||||
|
||||
/// <summary>
|
||||
/// The number of DLLs which weren't assemblies. (E.g. C++).
|
||||
/// </summary>
|
||||
public int NonAssemblyCount => failedDlls.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Given an assembly id, determine its full info.
|
||||
/// </summary>
|
||||
/// <param name="id">The given assembly id.</param>
|
||||
/// <returns>The information about the assembly.</returns>
|
||||
public AssemblyInfo ResolveReference(string id)
|
||||
{
|
||||
// Fast path if we've already seen this before.
|
||||
if (failedReferences.Contains(id))
|
||||
return AssemblyInfo.Invalid;
|
||||
|
||||
var query = AssemblyInfo.MakeFromId(id);
|
||||
id = query.Id; // Sanitise the id.
|
||||
|
||||
// Look up the id in our references map.
|
||||
AssemblyInfo result;
|
||||
if (references.TryGetValue(id, out result))
|
||||
{
|
||||
// The string is in the references map.
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Attempt to load the reference from the GAC.
|
||||
try
|
||||
{
|
||||
var loadedAssembly = System.Reflection.Assembly.ReflectionOnlyLoad(id);
|
||||
|
||||
if (loadedAssembly != null)
|
||||
{
|
||||
// The assembly was somewhere we haven't indexed before.
|
||||
// Add this assembly to our index so that subsequent lookups are faster.
|
||||
|
||||
result = AssemblyInfo.MakeFromAssembly(loadedAssembly);
|
||||
references[id] = result;
|
||||
assemblyInfo[loadedAssembly.Location] = result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// A suitable assembly could not be found
|
||||
}
|
||||
catch (FileLoadException)
|
||||
{
|
||||
// The assembly cannot be loaded for some reason
|
||||
// e.g. The name is malformed.
|
||||
}
|
||||
catch (PlatformNotSupportedException)
|
||||
{
|
||||
// .NET Core does not have a GAC.
|
||||
}
|
||||
|
||||
// Fallback position - locate the assembly by its lower-case name only.
|
||||
var asmName = query.Name.ToLowerInvariant();
|
||||
|
||||
if (references.TryGetValue(asmName, out result))
|
||||
{
|
||||
references[asmName] = result; // Speed up the next time the same string is resolved
|
||||
return result;
|
||||
}
|
||||
|
||||
failedReferences.Add(id); // Fail early next time
|
||||
|
||||
return AssemblyInfo.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All the assemblies we have indexed.
|
||||
/// </summary>
|
||||
public IEnumerable<AssemblyInfo> AllAssemblies => assemblyInfo.Select(a => a.Value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the assembly info of a pre-cached assembly.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filename to query.</param>
|
||||
/// <returns>The assembly info.</returns>
|
||||
public AssemblyInfo GetAssemblyInfo(string filepath) => assemblyInfo[filepath];
|
||||
|
||||
// List of pending DLLs to index.
|
||||
readonly List<string> dlls = new List<string>();
|
||||
|
||||
// Map from filename to assembly info.
|
||||
readonly Dictionary<string, AssemblyInfo> assemblyInfo = new Dictionary<string, AssemblyInfo>();
|
||||
|
||||
// List of DLLs which are not assemblies.
|
||||
// We probably don't need to keep this
|
||||
readonly List<string> failedDlls = new List<string>();
|
||||
|
||||
// Map from assembly id (in various formats) to the full info.
|
||||
readonly Dictionary<string, AssemblyInfo> references = new Dictionary<string, AssemblyInfo>();
|
||||
|
||||
// Set of failed assembly ids.
|
||||
readonly HashSet<string> failedReferences = new HashSet<string>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Semmle.BuildAnalyser
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores information about an assembly file (DLL).
|
||||
/// </summary>
|
||||
sealed class AssemblyInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The file containing the assembly.
|
||||
/// </summary>
|
||||
public string Filename { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Was the information correctly determined?
|
||||
/// </summary>
|
||||
public bool Valid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The short name of this assembly.
|
||||
/// </summary>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The version number of this assembly.
|
||||
/// </summary>
|
||||
public System.Version Version { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The public key token of the assembly.
|
||||
/// </summary>
|
||||
public string PublicKeyToken { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The culture.
|
||||
/// </summary>
|
||||
public string Culture { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get/parse a canonical ID of this assembly.
|
||||
/// </summary>
|
||||
public string Id
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = Name;
|
||||
if (Version != null)
|
||||
result = string.Format("{0}, Version={1}", result, Version);
|
||||
if (Culture != null)
|
||||
result = string.Format("{0}, Culture={1}", result, Culture);
|
||||
if (PublicKeyToken != null)
|
||||
result = string.Format("{0}, PublicKeyToken={1}", result, PublicKeyToken);
|
||||
return result;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
var sections = value.Split(new string[] { ", " }, StringSplitOptions.None);
|
||||
|
||||
Name = sections.First();
|
||||
|
||||
foreach (var section in sections.Skip(1))
|
||||
{
|
||||
if (section.StartsWith("Version="))
|
||||
Version = new Version(section.Substring(8));
|
||||
else if (section.StartsWith("Culture="))
|
||||
Culture = section.Substring(8);
|
||||
else if (section.StartsWith("PublicKeyToken="))
|
||||
PublicKeyToken = section.Substring(15);
|
||||
// else: Some other field like processorArchitecture - ignore.
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => Id;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of canonical search strings for this assembly.
|
||||
/// </summary>
|
||||
public IEnumerable<string> IndexStrings
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return Id;
|
||||
if (Version != null)
|
||||
{
|
||||
if (Culture != null) yield return string.Format("{0}, Version={1}, Culture={2}", Name, Version, Culture);
|
||||
yield return string.Format("{0}, Version={1}", Name, Version);
|
||||
}
|
||||
yield return Name;
|
||||
yield return Name.ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an invalid assembly info (Valid==false).
|
||||
/// </summary>
|
||||
public static AssemblyInfo Invalid { get; } = new AssemblyInfo();
|
||||
|
||||
private AssemblyInfo() { }
|
||||
|
||||
/// <summary>
|
||||
/// Get AssemblyInfo from a loaded Assembly.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly.</param>
|
||||
/// <returns>Info about the assembly.</returns>
|
||||
public static AssemblyInfo MakeFromAssembly(Assembly assembly) => new AssemblyInfo() { Valid = true, Filename = assembly.Location, Id = assembly.FullName };
|
||||
|
||||
/// <summary>
|
||||
/// Parse an assembly name/Id into an AssemblyInfo,
|
||||
/// populating the available fields and leaving the others null.
|
||||
/// </summary>
|
||||
/// <param name="id">The assembly name/Id.</param>
|
||||
/// <returns>The deconstructed assembly info.</returns>
|
||||
public static AssemblyInfo MakeFromId(string id) => new AssemblyInfo() { Valid = true, Id = id };
|
||||
|
||||
/// <summary>
|
||||
/// Reads the assembly info from a file.
|
||||
/// This uses System.Reflection.Metadata, which is a very performant and low-level
|
||||
/// library. This is very convenient when scanning hundreds of DLLs at a time.
|
||||
/// </summary>
|
||||
/// <param name="filename">The full filename of the assembly.</param>
|
||||
/// <returns>The information about the assembly.</returns>
|
||||
public static AssemblyInfo ReadFromFile(string filename)
|
||||
{
|
||||
var result = new AssemblyInfo() { Filename = filename };
|
||||
try
|
||||
{
|
||||
/* This method is significantly faster and more lightweight than using
|
||||
* System.Reflection.Assembly.ReflectionOnlyLoadFrom. It also allows
|
||||
* loading the same assembly from different locations.
|
||||
*/
|
||||
using (var pereader = new System.Reflection.PortableExecutable.PEReader(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)))
|
||||
{
|
||||
var metadata = pereader.GetMetadata();
|
||||
unsafe
|
||||
{
|
||||
var reader = new System.Reflection.Metadata.MetadataReader(metadata.Pointer, metadata.Length);
|
||||
var def = reader.GetAssemblyDefinition();
|
||||
|
||||
// This is how you compute the public key token from the full public key.
|
||||
// The last 8 bytes of the SHA1 of the public key.
|
||||
var publicKey = reader.GetBlobBytes(def.PublicKey);
|
||||
var publicKeyToken = sha1.ComputeHash(publicKey);
|
||||
var publicKeyString = new StringBuilder();
|
||||
foreach (var b in publicKeyToken.Skip(12).Reverse())
|
||||
publicKeyString.AppendFormat("{0:x2}", b);
|
||||
|
||||
result.Name = reader.GetString(def.Name);
|
||||
result.Version = def.Version;
|
||||
result.Culture = def.Culture.IsNil ? "neutral" : reader.GetString(def.Culture);
|
||||
result.PublicKeyToken = publicKeyString.ToString();
|
||||
result.Valid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (BadImageFormatException)
|
||||
{
|
||||
// The DLL wasn't an assembly -> result.Valid = false.
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Some other failure -> result.Valid = false.
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static readonly SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,312 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Semmle.Util;
|
||||
using Semmle.Extraction.CSharp.Standalone;
|
||||
|
||||
namespace Semmle.BuildAnalyser
|
||||
{
|
||||
/// <summary>
|
||||
/// The output of a build analysis.
|
||||
/// </summary>
|
||||
interface IBuildAnalysis
|
||||
{
|
||||
/// <summary>
|
||||
/// Full filepaths of external references.
|
||||
/// </summary>
|
||||
IEnumerable<string> ReferenceFiles { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Full filepaths of C# source files from project files.
|
||||
/// </summary>
|
||||
IEnumerable<string> ProjectSourceFiles { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Full filepaths of C# source files in the filesystem.
|
||||
/// </summary>
|
||||
IEnumerable<string> AllSourceFiles { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The assembly IDs which could not be resolved.
|
||||
/// </summary>
|
||||
IEnumerable<string> UnresolvedReferences { get; }
|
||||
|
||||
/// <summary>
|
||||
/// List of source files referenced by projects but
|
||||
/// which were not found in the filesystem.
|
||||
/// </summary>
|
||||
IEnumerable<string> MissingSourceFiles { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main implementation of the build analysis.
|
||||
/// </summary>
|
||||
class BuildAnalysis : IBuildAnalysis
|
||||
{
|
||||
readonly AssemblyCache assemblyCache;
|
||||
readonly NugetPackages nuget;
|
||||
readonly IProgressMonitor progressMonitor;
|
||||
HashSet<string> usedReferences = new HashSet<string>();
|
||||
readonly HashSet<string> usedSources = new HashSet<string>();
|
||||
readonly HashSet<string> missingSources = new HashSet<string>();
|
||||
readonly Dictionary<string, string> unresolvedReferences = new Dictionary<string, string>();
|
||||
readonly DirectoryInfo sourceDir;
|
||||
int failedProjects, succeededProjects;
|
||||
readonly string[] allSources;
|
||||
int conflictedReferences = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Performs a C# build analysis.
|
||||
/// </summary>
|
||||
/// <param name="options">Analysis options from the command line.</param>
|
||||
/// <param name="progress">Display of analysis progress.</param>
|
||||
public BuildAnalysis(Options options, IProgressMonitor progress)
|
||||
{
|
||||
progressMonitor = progress;
|
||||
sourceDir = new DirectoryInfo(options.SrcDir);
|
||||
|
||||
progressMonitor.FindingFiles(options.SrcDir);
|
||||
|
||||
allSources = sourceDir.GetFiles("*.cs", SearchOption.AllDirectories).
|
||||
Select(d => d.FullName).
|
||||
Where(d => !options.ExcludesFile(d)).
|
||||
ToArray();
|
||||
|
||||
var dllDirNames = options.DllDirs.Select(Path.GetFullPath);
|
||||
|
||||
if (options.UseNuGet)
|
||||
{
|
||||
nuget = new NugetPackages(sourceDir.FullName);
|
||||
ReadNugetFiles();
|
||||
dllDirNames = dllDirNames.Concat(Enumerators.Singleton(nuget.PackageDirectory));
|
||||
}
|
||||
|
||||
// Find DLLs in the .Net Framework
|
||||
if (options.ScanNetFrameworkDlls)
|
||||
{
|
||||
dllDirNames = dllDirNames.Concat(Runtime.Runtimes.Take(1));
|
||||
}
|
||||
|
||||
assemblyCache = new BuildAnalyser.AssemblyCache(dllDirNames, progress);
|
||||
|
||||
// Analyse all .csproj files in the source tree.
|
||||
if (options.SolutionFile != null)
|
||||
{
|
||||
AnalyseSolution(options.SolutionFile);
|
||||
}
|
||||
else if (options.AnalyseCsProjFiles)
|
||||
{
|
||||
AnalyseProjectFiles();
|
||||
}
|
||||
|
||||
if (!options.AnalyseCsProjFiles)
|
||||
{
|
||||
usedReferences = new HashSet<string>(assemblyCache.AllAssemblies.Select(a => a.Filename));
|
||||
}
|
||||
|
||||
ResolveConflicts();
|
||||
|
||||
if (options.UseMscorlib)
|
||||
{
|
||||
UseReference(typeof(object).Assembly.Location);
|
||||
}
|
||||
|
||||
// Output the findings
|
||||
foreach (var r in usedReferences)
|
||||
{
|
||||
progressMonitor.ResolvedReference(r);
|
||||
}
|
||||
|
||||
foreach (var r in unresolvedReferences)
|
||||
{
|
||||
progressMonitor.UnresolvedReference(r.Key, r.Value);
|
||||
}
|
||||
|
||||
progressMonitor.Summary(
|
||||
AllSourceFiles.Count(),
|
||||
ProjectSourceFiles.Count(),
|
||||
MissingSourceFiles.Count(),
|
||||
ReferenceFiles.Count(),
|
||||
UnresolvedReferences.Count(),
|
||||
conflictedReferences,
|
||||
succeededProjects + failedProjects,
|
||||
failedProjects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves conflicts between all of the resolved references.
|
||||
/// If the same assembly name is duplicated with different versions,
|
||||
/// resolve to the higher version number.
|
||||
/// </summary>
|
||||
void ResolveConflicts()
|
||||
{
|
||||
var sortedReferences = usedReferences.
|
||||
Select(r => assemblyCache.GetAssemblyInfo(r)).
|
||||
OrderBy(r => r.Version).
|
||||
ToArray();
|
||||
|
||||
Dictionary<string, AssemblyInfo> finalAssemblyList = new Dictionary<string, AssemblyInfo>();
|
||||
|
||||
// Pick the highest version for each assembly name
|
||||
foreach (var r in sortedReferences)
|
||||
finalAssemblyList[r.Name] = r;
|
||||
|
||||
// Update the used references list
|
||||
usedReferences = new HashSet<string>(finalAssemblyList.Select(r => r.Value.Filename));
|
||||
|
||||
// Report the results
|
||||
foreach (var r in sortedReferences)
|
||||
{
|
||||
var resolvedInfo = finalAssemblyList[r.Name];
|
||||
if (resolvedInfo.Version != r.Version)
|
||||
{
|
||||
progressMonitor.ResolvedConflict(r.Id, resolvedInfo.Id);
|
||||
++conflictedReferences;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find and restore NuGet packages.
|
||||
/// </summary>
|
||||
void ReadNugetFiles()
|
||||
{
|
||||
nuget.FindPackages();
|
||||
nuget.InstallPackages(progressMonitor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Store that a particular reference file is used.
|
||||
/// </summary>
|
||||
/// <param name="reference">The filename of the reference.</param>
|
||||
void UseReference(string reference)
|
||||
{
|
||||
usedReferences.Add(reference);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Store that a particular source file is used (by a project file).
|
||||
/// </summary>
|
||||
/// <param name="sourceFile">The source file.</param>
|
||||
void UseSource(FileInfo sourceFile)
|
||||
{
|
||||
if (sourceFile.Exists)
|
||||
{
|
||||
usedSources.Add(sourceFile.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
missingSources.Add(sourceFile.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of resolved reference files.
|
||||
/// </summary>
|
||||
public IEnumerable<string> ReferenceFiles => this.usedReferences;
|
||||
|
||||
/// <summary>
|
||||
/// The list of source files used in projects.
|
||||
/// </summary>
|
||||
public IEnumerable<string> ProjectSourceFiles => usedSources;
|
||||
|
||||
/// <summary>
|
||||
/// All of the source files in the source directory.
|
||||
/// </summary>
|
||||
public IEnumerable<string> AllSourceFiles => allSources;
|
||||
|
||||
/// <summary>
|
||||
/// List of assembly IDs which couldn't be resolved.
|
||||
/// </summary>
|
||||
public IEnumerable<string> UnresolvedReferences => this.unresolvedReferences.Select(r => r.Key);
|
||||
|
||||
/// <summary>
|
||||
/// List of source files which were mentioned in project files but
|
||||
/// do not exist on the file system.
|
||||
/// </summary>
|
||||
public IEnumerable<string> MissingSourceFiles => missingSources;
|
||||
|
||||
/// <summary>
|
||||
/// Record that a particular reference couldn't be resolved.
|
||||
/// Note that this records at most one project file per missing reference.
|
||||
/// </summary>
|
||||
/// <param name="id">The assembly ID.</param>
|
||||
/// <param name="projectFile">The project file making the reference.</param>
|
||||
void UnresolvedReference(string id, string projectFile)
|
||||
{
|
||||
unresolvedReferences[id] = projectFile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an analysis of all .csproj files.
|
||||
/// </summary>
|
||||
void AnalyseProjectFiles()
|
||||
{
|
||||
AnalyseProjectFiles(sourceDir.GetFiles("*.csproj", SearchOption.AllDirectories));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all the source files and references from the given list of projects.
|
||||
/// </summary>
|
||||
/// <param name="projectFiles">The list of projects to analyse.</param>
|
||||
void AnalyseProjectFiles(FileInfo[] projectFiles)
|
||||
{
|
||||
progressMonitor.AnalysingProjectFiles(projectFiles.Count());
|
||||
|
||||
foreach (var proj in projectFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
var csProj = new CsProjFile(proj);
|
||||
|
||||
foreach (var @ref in csProj.References)
|
||||
{
|
||||
AssemblyInfo resolved = assemblyCache.ResolveReference(@ref);
|
||||
if (!resolved.Valid)
|
||||
{
|
||||
UnresolvedReference(@ref, proj.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
UseReference(resolved.Filename);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var src in csProj.Sources)
|
||||
{
|
||||
// Make a note of which source files the projects use.
|
||||
// This information doesn't affect the build but is dumped
|
||||
// as diagnostic output.
|
||||
UseSource(new FileInfo(src));
|
||||
}
|
||||
++succeededProjects;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
++failedProjects;
|
||||
progressMonitor.FailedProjectFile(proj.FullName, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete packages directory.
|
||||
/// </summary>
|
||||
public void Cleanup()
|
||||
{
|
||||
if (nuget != null) nuget.Cleanup(progressMonitor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analyse all project files in a given solution only.
|
||||
/// </summary>
|
||||
/// <param name="solutionFile">The filename of the solution.</param>
|
||||
public void AnalyseSolution(string solutionFile)
|
||||
{
|
||||
var sln = new SolutionFile(solutionFile);
|
||||
AnalyseProjectFiles(sln.Projects.Select(p => new FileInfo(p)).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
|
||||
namespace Semmle.BuildAnalyser
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a .csproj file and reads information from it.
|
||||
/// </summary>
|
||||
class CsProjFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads the .csproj file.
|
||||
/// </summary>
|
||||
/// <param name="filename">The .csproj file.</param>
|
||||
public CsProjFile(FileInfo filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This can fail if the .csproj is invalid or has
|
||||
// unrecognised content or is the wrong version.
|
||||
// This currently always fails on Linux because
|
||||
// Microsoft.Build is not cross platform.
|
||||
ReadMsBuildProject(filename);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// There was some reason why the project couldn't be loaded.
|
||||
// Fall back to reading the Xml document directly.
|
||||
// This method however doesn't handle variable expansion.
|
||||
ReadProjectFileAsXml(filename);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the .csproj file using Microsoft Build.
|
||||
/// This occasionally fails if the project file is incompatible for some reason,
|
||||
/// and there seems to be no way to make it succeed. Fails on Linux.
|
||||
/// </summary>
|
||||
/// <param name="filename">The file to read.</param>
|
||||
public void ReadMsBuildProject(FileInfo filename)
|
||||
{
|
||||
var msbuildProject = new Microsoft.Build.Execution.ProjectInstance(filename.FullName);
|
||||
|
||||
references = msbuildProject.
|
||||
Items.
|
||||
Where(item => item.ItemType == "Reference").
|
||||
Select(item => item.EvaluatedInclude).
|
||||
ToArray();
|
||||
|
||||
csFiles = msbuildProject.Items
|
||||
.Where(item => item.ItemType == "Compile")
|
||||
.Select(item => item.GetMetadataValue("FullPath"))
|
||||
.Where(fn => fn.EndsWith(".cs"))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the .csproj file directly as XML.
|
||||
/// This doesn't handle variables etc, and should only used as a
|
||||
/// fallback if ReadMsBuildProject() fails.
|
||||
/// </summary>
|
||||
/// <param name="filename">The .csproj file.</param>
|
||||
public void ReadProjectFileAsXml(FileInfo filename)
|
||||
{
|
||||
var projFile = new XmlDocument();
|
||||
var mgr = new XmlNamespaceManager(projFile.NameTable);
|
||||
mgr.AddNamespace("msbuild", "http://schemas.microsoft.com/developer/msbuild/2003");
|
||||
projFile.Load(filename.FullName);
|
||||
var projDir = filename.Directory;
|
||||
var root = projFile.DocumentElement;
|
||||
|
||||
references =
|
||||
root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Reference/@Include", mgr).
|
||||
NodeList().
|
||||
Select(node => node.Value).
|
||||
ToArray();
|
||||
|
||||
var relativeCsIncludes =
|
||||
root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Compile/@Include", mgr).
|
||||
NodeList().
|
||||
Select(node => node.Value).
|
||||
ToArray();
|
||||
|
||||
csFiles = relativeCsIncludes.
|
||||
Select(cs => Path.DirectorySeparatorChar == '/' ? cs.Replace("\\", "/") : cs).
|
||||
Select(f => Path.GetFullPath(Path.Combine(projDir.FullName, f))).
|
||||
ToArray();
|
||||
}
|
||||
|
||||
string[] references;
|
||||
string[] csFiles;
|
||||
|
||||
/// <summary>
|
||||
/// The list of references as a list of assembly IDs.
|
||||
/// </summary>
|
||||
public IEnumerable<string> References => references;
|
||||
|
||||
/// <summary>
|
||||
/// The list of C# source files in full path format.
|
||||
/// </summary>
|
||||
public IEnumerable<string> Sources => csFiles;
|
||||
}
|
||||
|
||||
static class XmlNodeHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to convert an XmlNodeList into an IEnumerable.
|
||||
/// This allows it to be used with Linq.
|
||||
/// </summary>
|
||||
/// <param name="list">The list to convert.</param>
|
||||
/// <returns>A more useful data type.</returns>
|
||||
public static IEnumerable<XmlNode> NodeList(this XmlNodeList list)
|
||||
{
|
||||
foreach (var i in list)
|
||||
yield return i as XmlNode;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Semmle.BuildAnalyser
|
||||
{
|
||||
/// <summary>
|
||||
/// Manage the downloading of NuGet packages.
|
||||
/// Locates packages in a source tree and downloads all of the
|
||||
/// referenced assemblies to a temp folder.
|
||||
/// </summary>
|
||||
class NugetPackages
|
||||
{
|
||||
/// <summary>
|
||||
/// Create the package manager for a specified source tree.
|
||||
/// </summary>
|
||||
/// <param name="sourceDir">The source directory.</param>
|
||||
public NugetPackages(string sourceDir)
|
||||
{
|
||||
SourceDirectory = sourceDir;
|
||||
PackageDirectory = computeTempDirectory(sourceDir);
|
||||
|
||||
// Expect nuget.exe to be in a `nuget` directory under the directory containing this exe.
|
||||
var currentAssembly = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
nugetExe = Path.Combine(Path.GetDirectoryName(currentAssembly), "nuget", "nuget.exe");
|
||||
|
||||
if (!File.Exists(nugetExe))
|
||||
throw new FileNotFoundException(string.Format("NuGet could not be found at {0}", nugetExe));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Locate all NuGet packages but don't download them yet.
|
||||
/// </summary>
|
||||
public void FindPackages()
|
||||
{
|
||||
packages = new DirectoryInfo(SourceDirectory).
|
||||
EnumerateFiles("packages.config", SearchOption.AllDirectories).
|
||||
ToArray();
|
||||
}
|
||||
|
||||
// List of package files to download.
|
||||
FileInfo[] packages;
|
||||
|
||||
/// <summary>
|
||||
/// The list of package files.
|
||||
/// </summary>
|
||||
public IEnumerable<FileInfo> PackageFiles => packages;
|
||||
|
||||
// Whether to delete the packages directory prior to each run.
|
||||
// Makes each build more reproducible.
|
||||
const bool cleanupPackages = true;
|
||||
|
||||
public void Cleanup(IProgressMonitor pm)
|
||||
{
|
||||
var packagesDirectory = new DirectoryInfo(PackageDirectory);
|
||||
|
||||
if (packagesDirectory.Exists)
|
||||
{
|
||||
try
|
||||
{
|
||||
packagesDirectory.Delete(true);
|
||||
}
|
||||
catch (System.IO.IOException ex)
|
||||
{
|
||||
pm.Warning(string.Format("Couldn't delete package directory - it's probably held open by something else: {0}", ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download the packages to the temp folder.
|
||||
/// </summary>
|
||||
/// <param name="pm">The progress monitor used for reporting errors etc.</param>
|
||||
public void InstallPackages(IProgressMonitor pm)
|
||||
{
|
||||
if (cleanupPackages)
|
||||
{
|
||||
Cleanup(pm);
|
||||
}
|
||||
|
||||
var packagesDirectory = new DirectoryInfo(PackageDirectory);
|
||||
|
||||
if (!Directory.Exists(PackageDirectory))
|
||||
{
|
||||
packagesDirectory.Create();
|
||||
}
|
||||
|
||||
foreach (var package in packages)
|
||||
{
|
||||
RestoreNugetPackage(package.FullName, pm);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The source directory used.
|
||||
/// </summary>
|
||||
public string SourceDirectory
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The computed packages directory.
|
||||
/// This will be in the Temp location
|
||||
/// so as to not trample the source tree.
|
||||
/// </summary>
|
||||
public string PackageDirectory
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
readonly SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
|
||||
|
||||
/// <summary>
|
||||
/// Computes a unique temp directory for the packages associated
|
||||
/// with this source tree. Use a SHA1 of the directory name.
|
||||
/// </summary>
|
||||
/// <param name="srcDir"></param>
|
||||
/// <returns>The full path of the temp directory.</returns>
|
||||
string computeTempDirectory(string srcDir)
|
||||
{
|
||||
var bytes = Encoding.Unicode.GetBytes(srcDir);
|
||||
|
||||
var sha = sha1.ComputeHash(bytes);
|
||||
var sb = new StringBuilder();
|
||||
foreach (var b in sha.Take(8))
|
||||
sb.AppendFormat("{0:x2}", b);
|
||||
|
||||
return Path.Combine(Path.GetTempPath(), "Semmle", "packages", sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restore all files in a specified package.
|
||||
/// </summary>
|
||||
/// <param name="package">The package file.</param>
|
||||
/// <param name="pm">Where to log progress/errors.</param>
|
||||
void RestoreNugetPackage(string package, IProgressMonitor pm)
|
||||
{
|
||||
pm.NugetInstall(package);
|
||||
|
||||
/* Use nuget.exe to install a package.
|
||||
* Note that there is a clutch of NuGet assemblies which could be used to
|
||||
* invoke this directly, which would arguably be nicer. However they are
|
||||
* really unwieldy and this solution works for now.
|
||||
*/
|
||||
|
||||
string exe, args;
|
||||
if (Util.Win32.IsWindows())
|
||||
{
|
||||
exe = nugetExe;
|
||||
args = string.Format("install -OutputDirectory {0} {1}", PackageDirectory, package);
|
||||
}
|
||||
else
|
||||
{
|
||||
exe = "mono";
|
||||
args = string.Format("{0} install -OutputDirectory {1} {2}", nugetExe, PackageDirectory, package);
|
||||
}
|
||||
|
||||
var pi = new ProcessStartInfo(exe, args)
|
||||
{
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using (var p = Process.Start(pi))
|
||||
{
|
||||
string output = p.StandardOutput.ReadToEnd();
|
||||
string error = p.StandardError.ReadToEnd();
|
||||
|
||||
p.WaitForExit();
|
||||
if (p.ExitCode != 0)
|
||||
{
|
||||
pm.FailedNugetCommand(pi.FileName, pi.Arguments, output + error);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
when (e is System.ComponentModel.Win32Exception || e is FileNotFoundException)
|
||||
{
|
||||
pm.FailedNugetCommand(pi.FileName, pi.Arguments, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
readonly string nugetExe;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
using Semmle.Util.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Semmle.Util;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Standalone
|
||||
{
|
||||
/// <summary>
|
||||
/// The options controlling standalone extraction.
|
||||
/// </summary>
|
||||
public sealed class Options : CommonOptions
|
||||
{
|
||||
public override bool handleFlag(string key, bool value)
|
||||
{
|
||||
switch(key)
|
||||
{
|
||||
case "silent":
|
||||
Verbosity = value ? Verbosity.Off : Verbosity.Info;
|
||||
return true;
|
||||
case "help":
|
||||
Help = true;
|
||||
return true;
|
||||
case "dry-run":
|
||||
SkipExtraction = value;
|
||||
return true;
|
||||
case "skip-nuget":
|
||||
UseNuGet = !value;
|
||||
return true;
|
||||
case "all-references":
|
||||
AnalyseCsProjFiles = !value;
|
||||
return true;
|
||||
case "stdlib":
|
||||
UseMscorlib = value;
|
||||
return true;
|
||||
case "skip-dotnet":
|
||||
ScanNetFrameworkDlls = !value;
|
||||
return true;
|
||||
default:
|
||||
return base.handleFlag(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool handleOption(string key, string value)
|
||||
{
|
||||
switch(key)
|
||||
{
|
||||
case "exclude":
|
||||
Excludes.Add(value);
|
||||
return true;
|
||||
case "references":
|
||||
DllDirs.Add(value);
|
||||
return true;
|
||||
default:
|
||||
return base.handleOption(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool handleArgument(string arg)
|
||||
{
|
||||
SolutionFile = arg;
|
||||
var fi = new FileInfo(SolutionFile);
|
||||
if (!fi.Exists)
|
||||
{
|
||||
System.Console.WriteLine("Error: The solution {0} does not exist", fi.FullName);
|
||||
Errors = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void invalidArgument(string argument)
|
||||
{
|
||||
System.Console.WriteLine($"Error: Invalid argument {argument}");
|
||||
Errors = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Files/patterns to exclude.
|
||||
/// </summary>
|
||||
public IList<string> Excludes = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The number of concurrent threads to use.
|
||||
/// </summary>
|
||||
public int NumberOfThreads = Semmle.Extraction.Extractor.DefaultNumberOfThreads;
|
||||
|
||||
/// <summary>
|
||||
/// The directory containing the source code;
|
||||
/// </summary>
|
||||
public readonly string SrcDir = System.IO.Directory.GetCurrentDirectory();
|
||||
|
||||
/// <summary>
|
||||
/// Whether to analyse NuGet packages.
|
||||
/// </summary>
|
||||
public bool UseNuGet = true;
|
||||
|
||||
/// <summary>
|
||||
/// Directories to search DLLs in.
|
||||
/// </summary>
|
||||
public IList<string> DllDirs = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Whether to search the .Net framework directory.
|
||||
/// </summary>
|
||||
public bool ScanNetFrameworkDlls = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use mscorlib as a reference.
|
||||
/// </summary>
|
||||
public bool UseMscorlib = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to search .csproj files.
|
||||
/// </summary>
|
||||
public bool AnalyseCsProjFiles = true;
|
||||
|
||||
/// <summary>
|
||||
/// The solution file to analyse, or null if not specified.
|
||||
/// </summary>
|
||||
public string SolutionFile;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the extraction phase should be skipped (dry-run).
|
||||
/// </summary>
|
||||
public bool SkipExtraction = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether errors were encountered parsing the arguments.
|
||||
/// </summary>
|
||||
public bool Errors = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to show help.
|
||||
/// </summary>
|
||||
public bool Help = false;
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether the given path should be excluded.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to query.</param>
|
||||
/// <returns>True iff the path matches an exclusion.</returns>
|
||||
public bool ExcludesFile(string path)
|
||||
{
|
||||
return Excludes.Any(ex => path.Contains(ex));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs the command line options to the console.
|
||||
/// </summary>
|
||||
public void ShowHelp(System.IO.TextWriter output)
|
||||
{
|
||||
output.WriteLine("C# standalone extractor\n\nExtracts a C# project in the current directory without performing a build.\n");
|
||||
output.WriteLine("Additional options:\n");
|
||||
output.WriteLine(" xxx.sln Restrict sources to given solution");
|
||||
output.WriteLine(" --exclude:xxx Exclude a file or directory (can be specified multiple times)");
|
||||
output.WriteLine(" --references:xxx Scan additional files or directories for assemblies (can be specified multiple times)");
|
||||
output.WriteLine(" --skip-dotnet Do not reference the .Net Framework");
|
||||
output.WriteLine(" --dry-run Stop before extraction");
|
||||
output.WriteLine(" --skip-nuget Do not download nuget packages");
|
||||
output.WriteLine(" --all-references Use all references (default is to only use references in .csproj files)");
|
||||
output.WriteLine(" --nostdlib Do not link mscorlib.dll (use only for extracting mscorlib itself)");
|
||||
output.WriteLine(" --threads:nnn Specify number of threads (default=CPU cores)");
|
||||
output.WriteLine(" --verbose Produce more output");
|
||||
output.WriteLine(" --pdb Cross-reference information from PDBs where available");
|
||||
}
|
||||
|
||||
private Options()
|
||||
{
|
||||
}
|
||||
|
||||
public static Options Create(string[] args)
|
||||
{
|
||||
var options = new Options();
|
||||
options.ParseArguments(args);
|
||||
return options;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Semmle.BuildAnalyser;
|
||||
using Semmle.Util.Logging;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Standalone
|
||||
{
|
||||
/// <summary>
|
||||
/// One independent run of the extractor.
|
||||
/// </summary>
|
||||
class Extraction
|
||||
{
|
||||
public Extraction(string directory)
|
||||
{
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
public readonly string directory;
|
||||
public readonly List<string> Sources = new List<string>();
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Searches for source/references and creates separate extractions.
|
||||
/// </summary>
|
||||
class Analysis
|
||||
{
|
||||
readonly ILogger logger;
|
||||
|
||||
public Analysis(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
// The extraction configuration for the entire project.
|
||||
Extraction projectExtraction;
|
||||
|
||||
public IEnumerable<string> References
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The extraction configuration.
|
||||
/// </summary>
|
||||
public Extraction Extraction => projectExtraction;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an extraction for the current directory
|
||||
/// and adds it to the list of all extractions.
|
||||
/// </summary>
|
||||
/// <param name="dir">The directory of the extraction.</param>
|
||||
/// <returns>The extraction.</returns>
|
||||
void CreateExtraction(string dir)
|
||||
{
|
||||
projectExtraction = new Extraction(dir);
|
||||
}
|
||||
|
||||
BuildAnalysis buildAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// Analyse projects/solution and resolves references.
|
||||
/// </summary>
|
||||
/// <param name="options">The build analysis options.</param>
|
||||
public void AnalyseProjects(Options options)
|
||||
{
|
||||
CreateExtraction(options.SrcDir);
|
||||
var progressMonitor = new ProgressMonitor(logger);
|
||||
buildAnalysis = new BuildAnalysis(options, progressMonitor);
|
||||
References = buildAnalysis.ReferenceFiles;
|
||||
projectExtraction.Sources.AddRange(options.SolutionFile == null ? buildAnalysis.AllSourceFiles : buildAnalysis.ProjectSourceFiles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete any Nuget assemblies.
|
||||
/// </summary>
|
||||
public void Cleanup()
|
||||
{
|
||||
buildAnalysis.Cleanup();
|
||||
}
|
||||
};
|
||||
|
||||
public class Program
|
||||
{
|
||||
static int Main(string[] args)
|
||||
{
|
||||
var options = Options.Create(args);
|
||||
var output = new ConsoleLogger(options.Verbosity);
|
||||
var a = new Analysis(output);
|
||||
|
||||
if (options.Help)
|
||||
{
|
||||
options.ShowHelp(System.Console.Out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (options.Errors)
|
||||
return 1;
|
||||
|
||||
output.Log(Severity.Info, "Running C# standalone extractor");
|
||||
a.AnalyseProjects(options);
|
||||
int sourceFiles = a.Extraction.Sources.Count();
|
||||
|
||||
if (sourceFiles == 0)
|
||||
{
|
||||
output.Log(Severity.Error, "No source files found");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!options.SkipExtraction)
|
||||
{
|
||||
output.Log(Severity.Info, "");
|
||||
output.Log(Severity.Info, "Extracting...");
|
||||
Extractor.ExtractStandalone(
|
||||
a.Extraction.Sources,
|
||||
a.References,
|
||||
new ExtractionProgress(output),
|
||||
new FileLogger(options.Verbosity, Extractor.GetCSharpLogPath()),
|
||||
options);
|
||||
output.Log(Severity.Info, "Extraction complete");
|
||||
}
|
||||
|
||||
a.Cleanup();
|
||||
return 0;
|
||||
}
|
||||
|
||||
class ExtractionProgress : IProgressMonitor
|
||||
{
|
||||
public ExtractionProgress(ILogger output)
|
||||
{
|
||||
logger = output;
|
||||
}
|
||||
|
||||
readonly ILogger logger;
|
||||
|
||||
public void Analysed(int item, int total, string source, string output, TimeSpan time, AnalysisAction action)
|
||||
{
|
||||
logger.Log(Severity.Info, "[{0}/{1}] {2} ({3})", item, total, source,
|
||||
action == AnalysisAction.Extracted ? time.ToString() : action == AnalysisAction.Excluded ? "excluded" : "up to date");
|
||||
}
|
||||
|
||||
public void MissingType(string type)
|
||||
{
|
||||
logger.Log(Severity.Debug, "Missing type {0}", type);
|
||||
}
|
||||
|
||||
public void MissingNamespace(string @namespace)
|
||||
{
|
||||
logger.Log(Severity.Info, "Missing namespace {0}", @namespace);
|
||||
}
|
||||
|
||||
public void MissingSummary(int missingTypes, int missingNamespaces)
|
||||
{
|
||||
logger.Log(Severity.Info, "Failed to resolve {0} types and {1} namespaces", missingTypes, missingNamespaces);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
using Semmle.Util.Logging;
|
||||
|
||||
namespace Semmle.BuildAnalyser
|
||||
{
|
||||
/// <summary>
|
||||
/// Callback for various events that may happen during the build analysis.
|
||||
/// </summary>
|
||||
interface IProgressMonitor
|
||||
{
|
||||
void FindingFiles(string dir);
|
||||
void UnresolvedReference(string id, string project);
|
||||
void AnalysingProjectFiles(int count);
|
||||
void FailedProjectFile(string filename, string reason);
|
||||
void FailedNugetCommand(string exe, string args, string message);
|
||||
void NugetInstall(string package);
|
||||
void ResolvedReference(string filename);
|
||||
void Summary(int existingSources, int usedSources, int missingSources, int references, int unresolvedReferences, int resolvedConflicts, int totalProjects, int failedProjects);
|
||||
void Warning(string message);
|
||||
void ResolvedConflict(string asm1, string asm2);
|
||||
void MissingProject(string projectFile);
|
||||
}
|
||||
|
||||
class ProgressMonitor : IProgressMonitor
|
||||
{
|
||||
readonly ILogger logger;
|
||||
|
||||
public ProgressMonitor(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public void FindingFiles(string dir)
|
||||
{
|
||||
logger.Log(Severity.Info, "Finding files in {0}...", dir);
|
||||
}
|
||||
|
||||
public void IndexingReferences(int count)
|
||||
{
|
||||
logger.Log(Severity.Info, "Indexing...");
|
||||
logger.Log(Severity.Debug, "Indexing {0} DLLs...", count);
|
||||
}
|
||||
|
||||
public void UnresolvedReference(string id, string project)
|
||||
{
|
||||
logger.Log(Severity.Info, "Unresolved reference {0}", id);
|
||||
logger.Log(Severity.Debug, "Unresolved {0} referenced by {1}", id, project);
|
||||
}
|
||||
|
||||
public void AnalysingProjectFiles(int count)
|
||||
{
|
||||
logger.Log(Severity.Info, "Analyzing project files...");
|
||||
}
|
||||
|
||||
public void FailedProjectFile(string filename, string reason)
|
||||
{
|
||||
logger.Log(Severity.Info, "Couldn't read project file {0}: {1}", filename, reason);
|
||||
}
|
||||
|
||||
public void FailedNugetCommand(string exe, string args, string message)
|
||||
{
|
||||
logger.Log(Severity.Info, "Command failed: {0} {1}", exe, args);
|
||||
logger.Log(Severity.Info, " {0}", message);
|
||||
}
|
||||
|
||||
public void NugetInstall(string package)
|
||||
{
|
||||
logger.Log(Severity.Info, "Restoring {0}...", package);
|
||||
}
|
||||
|
||||
public void ResolvedReference(string filename)
|
||||
{
|
||||
logger.Log(Severity.Info, "Resolved {0}", filename);
|
||||
}
|
||||
|
||||
public void Summary(int existingSources, int usedSources, int missingSources,
|
||||
int references, int unresolvedReferences, int resolvedConflicts, int totalProjects, int failedProjects)
|
||||
{
|
||||
logger.Log(Severity.Info, "");
|
||||
logger.Log(Severity.Info, "Build analysis summary:");
|
||||
logger.Log(Severity.Info, "{0, 6} source files in the filesystem", existingSources);
|
||||
logger.Log(Severity.Info, "{0, 6} source files in project files", usedSources);
|
||||
logger.Log(Severity.Info, "{0, 6} sources missing from project files", missingSources);
|
||||
logger.Log(Severity.Info, "{0, 6} resolved references", references);
|
||||
logger.Log(Severity.Info, "{0, 6} unresolved references", unresolvedReferences);
|
||||
logger.Log(Severity.Info, "{0, 6} resolved assembly conflicts", resolvedConflicts);
|
||||
logger.Log(Severity.Info, "{0, 6} projects", totalProjects);
|
||||
logger.Log(Severity.Info, "{0, 6} missing/failed projects", failedProjects);
|
||||
}
|
||||
|
||||
public void Warning(string message)
|
||||
{
|
||||
logger.Log(Severity.Warning, message);
|
||||
}
|
||||
|
||||
public void ResolvedConflict(string asm1, string asm2)
|
||||
{
|
||||
logger.Log(Severity.Info, "Resolved {0} as {1}", asm1, asm2);
|
||||
}
|
||||
|
||||
public void MissingProject(string projectFile)
|
||||
{
|
||||
logger.Log(Severity.Info, "Solution is missing {0}", projectFile);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Semmle.Extraction.CSharp.Standalone")]
|
||||
[assembly: AssemblyDescription("Standalone extractor for C#")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Semmle Ltd.")]
|
||||
[assembly: AssemblyProduct("Semmle.Extraction.CSharp.Standalone")]
|
||||
[assembly: AssemblyCopyright("Copyright © Semmle 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("bb71e9da-7e0a-43e8-989c-c8e87c828e7c")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -0,0 +1,74 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Standalone
|
||||
{
|
||||
/// <summary>
|
||||
/// Locates .NET Runtimes.
|
||||
/// </summary>
|
||||
static class Runtime
|
||||
{
|
||||
static string ExecutingRuntime => RuntimeEnvironment.GetRuntimeDirectory();
|
||||
|
||||
/// <summary>
|
||||
/// Locates .NET Core Runtimes.
|
||||
/// </summary>
|
||||
public static IEnumerable<string> CoreRuntimes
|
||||
{
|
||||
get
|
||||
{
|
||||
string[] dotnetDirs = { "/usr/share/dotnet", @"C:\Program Files\dotnet" };
|
||||
|
||||
foreach (var dir in dotnetDirs.Where(Directory.Exists))
|
||||
return Directory.EnumerateDirectories(Path.Combine(dir, "shared", "Microsoft.NETCore.App")).
|
||||
OrderByDescending(d => Path.GetFileName(d));
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Locates .NET Desktop Runtimes.
|
||||
/// This includes Mono and Microsoft.NET.
|
||||
/// </summary>
|
||||
public static IEnumerable<string> DesktopRuntimes
|
||||
{
|
||||
get
|
||||
{
|
||||
string[] monoDirs = { "/usr/lib/mono", @"C:\Program Files\Mono\lib\mono" };
|
||||
|
||||
if (Directory.Exists(@"C:\Windows\Microsoft.NET\Framework64"))
|
||||
{
|
||||
return System.IO.Directory.EnumerateDirectories(@"C:\Windows\Microsoft.NET\Framework64", "v*").
|
||||
OrderByDescending(d => Path.GetFileName(d));
|
||||
}
|
||||
|
||||
foreach (var dir in monoDirs.Where(Directory.Exists))
|
||||
{
|
||||
return System.IO.Directory.EnumerateDirectories(dir).
|
||||
Where(d => Char.IsDigit(Path.GetFileName(d)[0])).
|
||||
OrderByDescending(d => Path.GetFileName(d));
|
||||
}
|
||||
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> Runtimes
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var r in CoreRuntimes)
|
||||
yield return r;
|
||||
|
||||
foreach (var r in DesktopRuntimes)
|
||||
yield return r;
|
||||
|
||||
// A bad choice if it's the self-contained runtime distributed in odasa dist.
|
||||
yield return ExecutingRuntime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<AssemblyName>Semmle.Extraction.CSharp.Standalone</AssemblyName>
|
||||
<RootNamespace>Semmle.Extraction.CSharp.Standalone</RootNamespace>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Semmle.Extraction.CSharp\Semmle.Extraction.CSharp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build" Version="15.8.166" />
|
||||
<PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" />
|
||||
<PackageReference Include="System.Net.Primitives" Version="4.3.0" />
|
||||
<PackageReference Include="System.Security.Principal" Version="4.3.0" />
|
||||
<PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,68 @@
|
|||
using Microsoft.Build.Construction;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Semmle.BuildAnalyser
|
||||
{
|
||||
/// <summary>
|
||||
/// Access data in a .sln file.
|
||||
/// </summary>
|
||||
class SolutionFile
|
||||
{
|
||||
readonly Microsoft.Build.Construction.SolutionFile solutionFile;
|
||||
|
||||
/// <summary>
|
||||
/// Read the file.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename of the .sln.</param>
|
||||
public SolutionFile(string filename)
|
||||
{
|
||||
// SolutionFile.Parse() expects a rooted path.
|
||||
var fullPath = Path.GetFullPath(filename);
|
||||
solutionFile = Microsoft.Build.Construction.SolutionFile.Parse(fullPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects directly included in the .sln file.
|
||||
/// </summary>
|
||||
public IEnumerable<string> MsBuildProjects
|
||||
{
|
||||
get
|
||||
{
|
||||
return solutionFile.ProjectsInOrder.
|
||||
Where(p => p.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat).
|
||||
Select(p => p.AbsolutePath).
|
||||
Select(p => Path.DirectorySeparatorChar == '/' ? p.Replace("\\", "/") : p);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects included transitively via a subdirectory.
|
||||
/// </summary>
|
||||
public IEnumerable<string> NestedProjects
|
||||
{
|
||||
get
|
||||
{
|
||||
return solutionFile.ProjectsInOrder.
|
||||
Where(p => p.ProjectType == SolutionProjectType.SolutionFolder).
|
||||
Where(p => Directory.Exists(p.AbsolutePath)).
|
||||
SelectMany(p => new DirectoryInfo(p.AbsolutePath).EnumerateFiles("*.csproj", SearchOption.AllDirectories)).
|
||||
Select(f => f.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of projects which were mentioned but don't exist on disk.
|
||||
/// </summary>
|
||||
public IEnumerable<string> MissingProjects =>
|
||||
// Only projects in the solution file can be missing.
|
||||
// (NestedProjects are located on disk so always exist.)
|
||||
MsBuildProjects.Where(p => !File.Exists(p));
|
||||
|
||||
/// <summary>
|
||||
/// The list of project files.
|
||||
/// </summary>
|
||||
public IEnumerable<string> Projects => MsBuildProjects.Concat(NestedProjects);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,505 @@
|
|||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Semmle.Extraction.CSharp.Populators;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics;
|
||||
using Semmle.Util.Logging;
|
||||
using Semmle.Util;
|
||||
|
||||
namespace Semmle.Extraction.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Encapsulates a C# analysis task.
|
||||
/// </summary>
|
||||
public class Analyser : IDisposable
|
||||
{
|
||||
IExtractor extractor;
|
||||
|
||||
readonly Stopwatch stopWatch = new Stopwatch();
|
||||
|
||||
readonly IProgressMonitor progressMonitor;
|
||||
|
||||
public readonly ILogger Logger;
|
||||
|
||||
public Analyser(IProgressMonitor pm, ILogger logger)
|
||||
{
|
||||
Logger = logger;
|
||||
Logger.Log(Severity.Info, "EXTRACTION STARTING at {0}", DateTime.Now);
|
||||
stopWatch.Start();
|
||||
progressMonitor = pm;
|
||||
}
|
||||
|
||||
CSharpCompilation compilation;
|
||||
Layout layout;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the analyser.
|
||||
/// </summary>
|
||||
/// <param name="commandLineArguments">Arguments passed to csc.</param>
|
||||
/// <param name="compilationIn">The Roslyn compilation.</param>
|
||||
/// <param name="options">Extractor options.</param>
|
||||
public void Initialize(
|
||||
CSharpCommandLineArguments commandLineArguments,
|
||||
CSharpCompilation compilationIn,
|
||||
Options options)
|
||||
{
|
||||
compilation = compilationIn;
|
||||
|
||||
layout = new Layout();
|
||||
this.options = options;
|
||||
|
||||
extractor = new Extraction.Extractor(false, GetOutputName(compilation, commandLineArguments), Logger);
|
||||
|
||||
LogDiagnostics();
|
||||
SetReferencePaths();
|
||||
|
||||
CompilationErrors += FilteredDiagnostics.Count();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the map from assembly string to its filename.
|
||||
///
|
||||
/// Roslyn doesn't record the relationship between a filename and its assembly
|
||||
/// information, so we need to retrieve this information manually.
|
||||
/// </summary>
|
||||
void SetReferencePaths()
|
||||
{
|
||||
foreach (var reference in compilation.References.OfType<PortableExecutableReference>())
|
||||
{
|
||||
try
|
||||
{
|
||||
var refPath = reference.FilePath;
|
||||
|
||||
/* This method is significantly faster and more lightweight than using
|
||||
* System.Reflection.Assembly.ReflectionOnlyLoadFrom. It is also allows
|
||||
* loading the same assembly from different locations.
|
||||
*/
|
||||
using (var pereader = new System.Reflection.PortableExecutable.PEReader(new FileStream(refPath, FileMode.Open, FileAccess.Read, FileShare.Read)))
|
||||
{
|
||||
var metadata = pereader.GetMetadata();
|
||||
string assemblyIdentity;
|
||||
unsafe
|
||||
{
|
||||
var reader = new System.Reflection.Metadata.MetadataReader(metadata.Pointer, metadata.Length);
|
||||
var def = reader.GetAssemblyDefinition();
|
||||
assemblyIdentity = reader.GetString(def.Name) + " " + def.Version;
|
||||
}
|
||||
extractor.SetAssemblyFile(assemblyIdentity, refPath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
extractor.Message(new Message
|
||||
{
|
||||
exception = ex,
|
||||
message = string.Format("Exception reading reference file {0}: {1}",
|
||||
reference.FilePath, ex)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void InitializeStandalone(CSharpCompilation compilationIn, CommonOptions options)
|
||||
{
|
||||
compilation = compilationIn;
|
||||
layout = new Layout();
|
||||
extractor = new Extraction.Extractor(true, null, Logger);
|
||||
this.options = options;
|
||||
LogDiagnostics();
|
||||
SetReferencePaths();
|
||||
}
|
||||
|
||||
readonly HashSet<string> errorsToIgnore = new HashSet<string>
|
||||
{
|
||||
"CS7027", // Code signing failure
|
||||
"CS1589", // XML referencing not supported
|
||||
"CS1569" // Error writing XML documentation
|
||||
};
|
||||
|
||||
IEnumerable<Diagnostic> FilteredDiagnostics
|
||||
{
|
||||
get
|
||||
{
|
||||
return extractor == null || extractor.Standalone || compilation == null ? Enumerable.Empty<Diagnostic>() :
|
||||
compilation.
|
||||
GetDiagnostics().
|
||||
Where(e => e.Severity >= DiagnosticSeverity.Error && !errorsToIgnore.Contains(e.Id));
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> MissingTypes => extractor.MissingTypes;
|
||||
|
||||
public IEnumerable<string> MissingNamespaces => extractor.MissingNamespaces;
|
||||
|
||||
/// <summary>
|
||||
/// Determine the path of the output dll/exe.
|
||||
/// </summary>
|
||||
/// <param name="compilation">Information about the compilation.</param>
|
||||
/// <param name="cancel">Cancellation token required.</param>
|
||||
/// <returns>The filename.</returns>
|
||||
static string GetOutputName(CSharpCompilation compilation,
|
||||
CSharpCommandLineArguments commandLineArguments)
|
||||
{
|
||||
// There's no apparent way to access the output filename from the compilation,
|
||||
// so we need to re-parse the command line arguments.
|
||||
|
||||
if (commandLineArguments.OutputFileName == null)
|
||||
{
|
||||
// No output specified: Use name based on first filename
|
||||
var entry = compilation.GetEntryPoint(System.Threading.CancellationToken.None);
|
||||
if (entry == null)
|
||||
{
|
||||
if (compilation.SyntaxTrees.Length == 0)
|
||||
throw new ArgumentNullException("No source files seen");
|
||||
|
||||
// Probably invalid, but have a go anyway.
|
||||
var entryPointFile = compilation.SyntaxTrees.First().FilePath;
|
||||
return Path.ChangeExtension(entryPointFile, ".exe");
|
||||
}
|
||||
else
|
||||
{
|
||||
var entryPointFilename = entry.Locations.First().SourceTree.FilePath;
|
||||
return Path.ChangeExtension(entryPointFilename, ".exe");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return Path.Combine(commandLineArguments.OutputDirectory, commandLineArguments.OutputFileName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform an analysis on a source file/syntax tree.
|
||||
/// </summary>
|
||||
/// <param name="tree">Syntax tree to analyse.</param>
|
||||
public void AnalyseTree(SyntaxTree tree)
|
||||
{
|
||||
extractionTasks.Add(() => DoExtractTree(tree));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform an analysis on an assembly.
|
||||
/// </summary>
|
||||
/// <param name="assembly">Assembly to analyse.</param>
|
||||
void AnalyseAssembly(PortableExecutableReference assembly)
|
||||
{
|
||||
// CIL first - it takes longer.
|
||||
if (options.CIL)
|
||||
extractionTasks.Add(() => DoExtractCIL(assembly));
|
||||
extractionTasks.Add(() => DoAnalyseAssembly(assembly));
|
||||
}
|
||||
|
||||
readonly object progressMutex = new object();
|
||||
int taskCount = 0;
|
||||
|
||||
CommonOptions options;
|
||||
|
||||
static bool FileIsUpToDate(string src, string dest)
|
||||
{
|
||||
return File.Exists(dest) &&
|
||||
File.GetLastWriteTime(dest) >= File.GetLastWriteTime(src);
|
||||
}
|
||||
|
||||
bool FileIsCached(string src, string dest)
|
||||
{
|
||||
return options.Cache && FileIsUpToDate(src, dest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract an assembly to a new trap file.
|
||||
/// If the trap file exists, skip extraction to avoid duplicating
|
||||
/// extraction within the snapshot.
|
||||
/// </summary>
|
||||
/// <param name="r">The assembly to extract.</param>
|
||||
void DoAnalyseAssembly(PortableExecutableReference r)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
var assemblyPath = r.FilePath;
|
||||
var projectLayout = layout.LookupProjectOrDefault(assemblyPath);
|
||||
using (var trapWriter = projectLayout.CreateTrapWriter(Logger, assemblyPath, true))
|
||||
{
|
||||
var skipExtraction = FileIsCached(assemblyPath, trapWriter.TrapFile);
|
||||
|
||||
if (!skipExtraction)
|
||||
{
|
||||
/* Note on parallel builds:
|
||||
*
|
||||
* The trap writer and source archiver both perform atomic moves
|
||||
* of the file to the final destination.
|
||||
*
|
||||
* If the same source file or trap file are generated concurrently
|
||||
* (by different parallel invocations of the extractor), then
|
||||
* last one wins.
|
||||
*
|
||||
* Specifically, if two assemblies are analysed concurrently in a build,
|
||||
* then there is a small amount of duplicated work but the output should
|
||||
* still be correct.
|
||||
*/
|
||||
|
||||
// compilation.Clone() reduces memory footprint by allowing the symbols
|
||||
// in c to be garbage collected.
|
||||
Compilation c = compilation.Clone();
|
||||
|
||||
var assembly = c.GetAssemblyOrModuleSymbol(r) as IAssemblySymbol;
|
||||
|
||||
if (assembly != null)
|
||||
{
|
||||
var cx = new Context(extractor, c, trapWriter, new AssemblyScope(assembly, assemblyPath));
|
||||
|
||||
foreach (var module in assembly.Modules)
|
||||
{
|
||||
AnalyseNamespace(cx, module.GlobalNamespace);
|
||||
}
|
||||
|
||||
cx.PopulateAll();
|
||||
}
|
||||
}
|
||||
|
||||
ReportProgress(assemblyPath, trapWriter.TrapFile, stopwatch.Elapsed, skipExtraction ? AnalysisAction.UpToDate : AnalysisAction.Extracted);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log(Severity.Error, " Unhandled exception analyzing {0}: {1}", r.FilePath, ex);
|
||||
}
|
||||
}
|
||||
|
||||
void DoExtractCIL(PortableExecutableReference r)
|
||||
{
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
string trapFile;
|
||||
bool extracted;
|
||||
CIL.Entities.Assembly.ExtractCIL(layout, r.FilePath, Logger, !options.Cache, options.PDB, out trapFile, out extracted);
|
||||
stopwatch.Stop();
|
||||
ReportProgress(r.FilePath, trapFile, stopwatch.Elapsed, extracted ? AnalysisAction.Extracted : AnalysisAction.UpToDate);
|
||||
}
|
||||
|
||||
void AnalyseNamespace(Context cx, INamespaceSymbol ns)
|
||||
{
|
||||
foreach (var memberNamespace in ns.GetNamespaceMembers())
|
||||
{
|
||||
AnalyseNamespace(cx, memberNamespace);
|
||||
}
|
||||
|
||||
foreach (var memberType in ns.GetTypeMembers())
|
||||
{
|
||||
Entities.Type.Create(cx, memberType).ExtractRecursive();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueue all reference analysis tasks.
|
||||
/// </summary>
|
||||
public void AnalyseReferences()
|
||||
{
|
||||
foreach (var r in compilation.References.OfType<PortableExecutableReference>())
|
||||
{
|
||||
AnalyseAssembly(r);
|
||||
}
|
||||
}
|
||||
|
||||
// The bulk of the extraction work, potentially executed in parallel.
|
||||
readonly List<Action> extractionTasks = new List<Action>();
|
||||
|
||||
void ReportProgress(string src, string output, TimeSpan time, AnalysisAction action)
|
||||
{
|
||||
lock (progressMutex)
|
||||
progressMonitor.Analysed(++taskCount, extractionTasks.Count, src, output, time, action);
|
||||
}
|
||||
|
||||
void DoExtractTree(SyntaxTree tree)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
var sourcePath = tree.FilePath;
|
||||
|
||||
var projectLayout = layout.LookupProjectOrNull(sourcePath);
|
||||
bool excluded = projectLayout == null;
|
||||
string trapPath = excluded ? "" : projectLayout.GetTrapPath(Logger, sourcePath);
|
||||
bool upToDate = false;
|
||||
|
||||
if (!excluded)
|
||||
{
|
||||
// compilation.Clone() is used to allow symbols to be garbage collected.
|
||||
using (var trapWriter = projectLayout.CreateTrapWriter(Logger, sourcePath, false))
|
||||
{
|
||||
upToDate = options.Fast && FileIsUpToDate(sourcePath, trapWriter.TrapFile);
|
||||
|
||||
if (!upToDate)
|
||||
{
|
||||
Context cx = new Context(extractor, compilation.Clone(), trapWriter, new SourceScope(tree));
|
||||
Populators.CompilationUnit.Extract(cx, tree.GetRoot());
|
||||
cx.PopulateAll();
|
||||
cx.ExtractComments(cx.CommentGenerator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReportProgress(sourcePath, trapPath, stopwatch.Elapsed, excluded ? AnalysisAction.Excluded : upToDate ? AnalysisAction.UpToDate : AnalysisAction.Extracted);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
extractor.Message(new Message { exception = ex, message = string.Format("Unhandled exception processing {0}: {1}", tree.FilePath, ex), severity = Severity.Error });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run all extraction tasks.
|
||||
/// </summary>
|
||||
/// <param name="numberOfThreads">The number of threads to use.</param>
|
||||
public void PerformExtraction(int numberOfThreads)
|
||||
{
|
||||
Parallel.Invoke(
|
||||
new ParallelOptions { MaxDegreeOfParallelism = numberOfThreads },
|
||||
extractionTasks.ToArray());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
stopWatch.Stop();
|
||||
Logger.Log(Severity.Info, " Peak working set = {0} MB", Process.GetCurrentProcess().PeakWorkingSet64 / (1024 * 1024));
|
||||
|
||||
if (TotalErrors > 0)
|
||||
Logger.Log(Severity.Info, "EXTRACTION FAILED with {0} error{1} in {2}", TotalErrors, TotalErrors == 1 ? "" : "s", stopWatch.Elapsed);
|
||||
else
|
||||
Logger.Log(Severity.Info, "EXTRACTION SUCCEEDED in {0}", stopWatch.Elapsed);
|
||||
|
||||
Logger.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of errors encountered during extraction.
|
||||
/// </summary>
|
||||
public int ExtractorErrors => extractor == null ? 0 : extractor.Errors;
|
||||
|
||||
/// <summary>
|
||||
/// Number of errors encountered by the compiler.
|
||||
/// </summary>
|
||||
public int CompilationErrors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total number of errors reported.
|
||||
/// </summary>
|
||||
public int TotalErrors => CompilationErrors + ExtractorErrors;
|
||||
|
||||
void AppendQuoted(StringBuilder sb, string s)
|
||||
{
|
||||
if (s.IndexOf(' ') != -1)
|
||||
sb.Append('\"').Append(s).Append('\"');
|
||||
else
|
||||
sb.Append(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs detailed information about this invocation,
|
||||
/// in the event that errors were detected.
|
||||
/// </summary>
|
||||
public void LogDiagnostics()
|
||||
{
|
||||
Logger.Log(Severity.Info, " Current working directory: {0}", Directory.GetCurrentDirectory());
|
||||
Logger.Log(Severity.Info, " Extractor: {0}", Environment.GetCommandLineArgs().First());
|
||||
if (extractor != null)
|
||||
Logger.Log(Severity.Info, " Extractor version: {0}", extractor.Version);
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(" Expanded command line: ");
|
||||
bool first = true;
|
||||
foreach (var arg in Environment.GetCommandLineArgs().Skip(1))
|
||||
{
|
||||
if (arg[0] == '@')
|
||||
{
|
||||
foreach (var line in File.ReadAllLines(arg.Substring(1)))
|
||||
{
|
||||
if (first) first = false;
|
||||
else sb.Append(" ");
|
||||
sb.Append(line);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (first) first = false;
|
||||
else sb.Append(" ");
|
||||
AppendQuoted(sb, arg);
|
||||
}
|
||||
}
|
||||
Logger.Log(Severity.Info, sb.ToString());
|
||||
|
||||
foreach (var error in FilteredDiagnostics)
|
||||
{
|
||||
Logger.Log(Severity.Error, " Compilation error: {0}", error);
|
||||
}
|
||||
|
||||
if (FilteredDiagnostics.Any())
|
||||
{
|
||||
foreach (var reference in compilation.References)
|
||||
{
|
||||
Logger.Log(Severity.Info, " Resolved reference {0}", reference.Display);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// What action was performed when extracting a file.
|
||||
/// </summary>
|
||||
public enum AnalysisAction
|
||||
{
|
||||
Extracted,
|
||||
UpToDate,
|
||||
Excluded
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback for various extraction events.
|
||||
/// (Used for display of progress).
|
||||
/// </summary>
|
||||
public interface IProgressMonitor
|
||||
{
|
||||
/// <summary>
|
||||
/// Callback that a particular item has been analysed.
|
||||
/// </summary>
|
||||
/// <param name="item">The item number being processed.</param>
|
||||
/// <param name="total">The total number of items to process.</param>
|
||||
/// <param name="source">The name of the item, e.g. a source file.</param>
|
||||
/// <param name="output">The name of the item being output, e.g. a trap file.</param>
|
||||
/// <param name="time">The time to extract the item.</param>
|
||||
/// <param name="action">What action was taken for the file.</param>
|
||||
void Analysed(int item, int total, string source, string output, TimeSpan time, AnalysisAction action);
|
||||
|
||||
/// <summary>
|
||||
/// A "using namespace" directive was seen but the given
|
||||
/// namespace could not be found.
|
||||
/// Only called once for each @namespace.
|
||||
/// </summary>
|
||||
/// <param name="namespace"></param>
|
||||
void MissingNamespace(string @namespace);
|
||||
|
||||
/// <summary>
|
||||
/// An ErrorType was found.
|
||||
/// Called once for each type name.
|
||||
/// </summary>
|
||||
/// <param name="type">The full/partial name of the type.</param>
|
||||
void MissingType(string type);
|
||||
|
||||
/// <summary>
|
||||
/// Report a summary of missing entities.
|
||||
/// </summary>
|
||||
/// <param name="types">The number of missing types.</param>
|
||||
/// <param name="namespaces">The number of missing using namespace declarations.</param>
|
||||
void MissingSummary(int types, int namespaces);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Semmle.Extraction.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies the compiler and framework from the command line arguments.
|
||||
/// --compiler specifies the compiler
|
||||
/// --framework specifies the .net framework
|
||||
/// </summary>
|
||||
public class CompilerVersion
|
||||
{
|
||||
const string csc_rsp = "csc.rsp";
|
||||
readonly string specifiedFramework = null;
|
||||
|
||||
/// <summary>
|
||||
/// The value specified by --compiler, or null.
|
||||
/// </summary>
|
||||
public string SpecifiedCompiler
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Why was the candidate exe rejected as a compiler?
|
||||
/// </summary>
|
||||
public string SkipReason
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Probes the compiler (if specified).
|
||||
/// </summary>
|
||||
/// <param name="options">The command line arguments.</param>
|
||||
public CompilerVersion(Options options)
|
||||
{
|
||||
SpecifiedCompiler = options.CompilerName;
|
||||
specifiedFramework = options.Framework;
|
||||
|
||||
if (SpecifiedCompiler != null)
|
||||
{
|
||||
if (!File.Exists(SpecifiedCompiler))
|
||||
{
|
||||
SkipExtractionBecause("the specified file does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
// Reads the file details from the .exe
|
||||
var versionInfo = FileVersionInfo.GetVersionInfo(SpecifiedCompiler);
|
||||
|
||||
var compilerDir = Path.GetDirectoryName(SpecifiedCompiler);
|
||||
bool known_compiler_name = versionInfo.OriginalFilename == "csc.exe" || versionInfo.OriginalFilename == "csc2.exe";
|
||||
bool copyright_microsoft = versionInfo.LegalCopyright != null && versionInfo.LegalCopyright.Contains("Microsoft");
|
||||
bool mscorlib_exists = File.Exists(Path.Combine(compilerDir, "mscorlib.dll"));
|
||||
|
||||
if (specifiedFramework == null && mscorlib_exists)
|
||||
{
|
||||
specifiedFramework = compilerDir;
|
||||
}
|
||||
|
||||
if (!known_compiler_name)
|
||||
{
|
||||
SkipExtractionBecause("the exe name is not recognised");
|
||||
}
|
||||
else if (!copyright_microsoft)
|
||||
{
|
||||
SkipExtractionBecause("the exe isn't copyright Microsoft");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkipExtractionBecause(string reason)
|
||||
{
|
||||
SkipExtraction = true;
|
||||
SkipReason = reason;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The directory containing the .Net Framework.
|
||||
/// </summary>
|
||||
public string FrameworkPath => specifiedFramework ?? RuntimeEnvironment.GetRuntimeDirectory();
|
||||
|
||||
/// <summary>
|
||||
/// The file csc.rsp.
|
||||
/// </summary>
|
||||
public string CscRsp => Path.Combine(FrameworkPath, csc_rsp);
|
||||
|
||||
/// <summary>
|
||||
/// Should we skip extraction?
|
||||
/// Only if csc.exe was specified but it wasn't a compiler.
|
||||
/// </summary>
|
||||
public bool SkipExtraction
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets additional reference directories - the compiler directory.
|
||||
/// </summary>
|
||||
public string AdditionalReferenceDirectories => SpecifiedCompiler != null ? Path.GetDirectoryName(SpecifiedCompiler) : null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
class Accessor : Method
|
||||
{
|
||||
protected Accessor(Context cx, IMethodSymbol init)
|
||||
: base(cx, init) { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property symbol associated with this accessor.
|
||||
/// </summary>
|
||||
IPropertySymbol PropertySymbol
|
||||
{
|
||||
get
|
||||
{
|
||||
// Usually, the property/indexer can be fetched from the associated symbol
|
||||
var prop = symbol.AssociatedSymbol as IPropertySymbol;
|
||||
if (prop != null)
|
||||
return prop;
|
||||
|
||||
// But for properties/indexers that implement explicit interfaces, Roslyn
|
||||
// does not properly populate `AssociatedSymbol`
|
||||
var props = symbol.ContainingType.GetMembers().OfType<IPropertySymbol>();
|
||||
props = props.Where(p => symbol.Equals(p.GetMethod) || symbol.Equals(p.SetMethod));
|
||||
return props.SingleOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
public new Accessor OriginalDefinition => Create(Context, symbol.OriginalDefinition);
|
||||
|
||||
public override void Populate()
|
||||
{
|
||||
PopulateMethod();
|
||||
ExtractModifiers();
|
||||
ContainingType.ExtractGenerics();
|
||||
|
||||
var prop = PropertySymbol;
|
||||
if (prop == null)
|
||||
{
|
||||
Context.ModelError(symbol, "Unhandled accessor associated symbol");
|
||||
return;
|
||||
}
|
||||
|
||||
var parent = Property.Create(Context, prop);
|
||||
int kind;
|
||||
Accessor unboundAccessor;
|
||||
if (symbol.Equals(prop.GetMethod))
|
||||
{
|
||||
kind = 1;
|
||||
unboundAccessor = Create(Context, prop.OriginalDefinition.GetMethod);
|
||||
}
|
||||
else if (symbol.Equals(prop.SetMethod))
|
||||
{
|
||||
kind = 2;
|
||||
unboundAccessor = Create(Context, prop.OriginalDefinition.SetMethod);
|
||||
}
|
||||
else
|
||||
{
|
||||
Context.ModelError(symbol, "Undhandled accessor kind");
|
||||
return;
|
||||
}
|
||||
|
||||
Context.Emit(Tuples.accessors(this, kind, symbol.Name, parent, unboundAccessor));
|
||||
|
||||
foreach (var l in Locations)
|
||||
Context.Emit(Tuples.accessor_location(this, l));
|
||||
|
||||
Overrides();
|
||||
|
||||
if (symbol.FromSource() && Block == null)
|
||||
{
|
||||
Context.Emit(Tuples.compiler_generated(this));
|
||||
}
|
||||
}
|
||||
|
||||
public new static Accessor Create(Context cx, IMethodSymbol symbol) =>
|
||||
AccessorFactory.Instance.CreateEntity(cx, symbol);
|
||||
|
||||
class AccessorFactory : ICachedEntityFactory<IMethodSymbol, Accessor>
|
||||
{
|
||||
public static readonly AccessorFactory Instance = new AccessorFactory();
|
||||
|
||||
public Accessor Create(Context cx, IMethodSymbol init) => new Accessor(cx, init);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.CSharp.Populators;
|
||||
using Semmle.Extraction.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
class Attribute : FreshEntity, IExpressionParentEntity
|
||||
{
|
||||
bool IExpressionParentEntity.IsTopLevelParent => true;
|
||||
|
||||
public Attribute(Context cx, AttributeData attribute, IEntity entity)
|
||||
: base(cx)
|
||||
{
|
||||
if (attribute.ApplicationSyntaxReference != null)
|
||||
{
|
||||
// !! Extract attributes from assemblies.
|
||||
// This is harder because the "expression" entities presume the
|
||||
// existence of a syntax tree. This is not the case for compiled
|
||||
// attributes.
|
||||
var syntax = attribute.ApplicationSyntaxReference.GetSyntax() as AttributeSyntax;
|
||||
ExtractAttribute(cx, syntax, attribute.AttributeClass, entity);
|
||||
}
|
||||
}
|
||||
|
||||
public Attribute(Context cx, AttributeSyntax attribute, IEntity entity)
|
||||
: base(cx)
|
||||
{
|
||||
var info = cx.GetSymbolInfo(attribute);
|
||||
ExtractAttribute(cx, attribute, info.Symbol.ContainingType, entity);
|
||||
}
|
||||
|
||||
void ExtractAttribute(Context cx, AttributeSyntax syntax, ITypeSymbol attributeClass, IEntity entity)
|
||||
{
|
||||
var type = Type.Create(cx, attributeClass);
|
||||
cx.Emit(Tuples.attributes(this, type.TypeRef, entity));
|
||||
|
||||
cx.Emit(Tuples.attribute_location(this, cx.Create(syntax.Name.GetLocation())));
|
||||
|
||||
if (cx.Extractor.OutputPath != null)
|
||||
cx.Emit(Tuples.attribute_location(this, Assembly.CreateOutputAssembly(cx)));
|
||||
|
||||
TypeMention.Create(cx, syntax.Name, this, type);
|
||||
|
||||
if (syntax.ArgumentList != null)
|
||||
{
|
||||
cx.PopulateLater(() =>
|
||||
{
|
||||
int child = 0;
|
||||
foreach (var arg in syntax.ArgumentList.Arguments)
|
||||
{
|
||||
Expression.Create(cx, arg.Expression, this, child++);
|
||||
}
|
||||
// !! Handle named arguments
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExtractAttributes(Context cx, ISymbol symbol, IEntity entity)
|
||||
{
|
||||
foreach (var attribute in symbol.GetAttributes())
|
||||
{
|
||||
new Attribute(cx, attribute, entity);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExtractAttributes(Context cx, IEnumerable<AttributeListSyntax> attributes, IEntity entity)
|
||||
{
|
||||
foreach (var attributeSyntax in attributes.SelectMany(l => l.Attributes))
|
||||
{
|
||||
new Attribute(cx, attributeSyntax, entity);
|
||||
}
|
||||
}
|
||||
|
||||
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
using Semmle.Extraction.CommentProcessing;
|
||||
using Semmle.Extraction.Entities;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
class CommentBlock : CachedEntity<ICommentBlock>
|
||||
{
|
||||
CommentBlock(Context cx, ICommentBlock init)
|
||||
: base(cx, init) { }
|
||||
|
||||
public override void Populate()
|
||||
{
|
||||
Context.Emit(Tuples.commentblock(this));
|
||||
int child = 0;
|
||||
Context.Emit(Tuples.commentblock_location(this, Context.Create(symbol.Location)));
|
||||
foreach (var l in symbol.CommentLines)
|
||||
{
|
||||
Context.Emit(Tuples.commentblock_child(this, (CommentLine)l, child++));
|
||||
}
|
||||
}
|
||||
|
||||
public override bool NeedsPopulation => true;
|
||||
|
||||
public override IId Id
|
||||
{
|
||||
get
|
||||
{
|
||||
var loc = Context.Create(symbol.Location);
|
||||
return new Key(loc, ";commentblock");
|
||||
}
|
||||
}
|
||||
|
||||
public override Microsoft.CodeAnalysis.Location ReportingLocation => symbol.Location;
|
||||
|
||||
public void BindTo(Label entity, Binding binding)
|
||||
{
|
||||
Context.Emit(Tuples.commentblock_binding(this, entity, binding));
|
||||
}
|
||||
|
||||
public static CommentBlock Create(Context cx, ICommentBlock block) => CommentBlockFactory.Instance.CreateEntity(cx, block);
|
||||
|
||||
class CommentBlockFactory : ICachedEntityFactory<ICommentBlock, CommentBlock>
|
||||
{
|
||||
public static readonly CommentBlockFactory Instance = new CommentBlockFactory();
|
||||
|
||||
public CommentBlock Create(Context cx, ICommentBlock init) => new CommentBlock(cx, init);
|
||||
}
|
||||
|
||||
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.NoLabel;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
using Semmle.Extraction.CommentProcessing;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Semmle.Extraction.Entities;
|
||||
using System;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
class CommentLine : CachedEntity<(Microsoft.CodeAnalysis.Location, string)>, ICommentLine
|
||||
{
|
||||
CommentLine(Context cx, Microsoft.CodeAnalysis.Location loc, CommentType type, string text, string raw)
|
||||
: base(cx, (loc, text))
|
||||
{
|
||||
Type = type;
|
||||
RawText = raw;
|
||||
}
|
||||
|
||||
public Microsoft.CodeAnalysis.Location Location => symbol.Item1;
|
||||
public CommentType Type { get; private set; }
|
||||
|
||||
public string Text { get { return symbol.Item2; } }
|
||||
public string RawText { get; private set; }
|
||||
|
||||
public static void Extract(Context cx, SyntaxTrivia trivia)
|
||||
{
|
||||
switch (trivia.Kind())
|
||||
{
|
||||
case SyntaxKind.SingleLineDocumentationCommentTrivia:
|
||||
/*
|
||||
This is actually a multi-line comment consisting of /// lines.
|
||||
So split it up.
|
||||
*/
|
||||
|
||||
var text = trivia.ToFullString();
|
||||
|
||||
var split = text.Split('\n');
|
||||
var currentLocation = trivia.GetLocation().SourceSpan.Start - 3;
|
||||
|
||||
for (int line = 0; line < split.Length - 1; ++line)
|
||||
{
|
||||
string fullLine = split[line];
|
||||
var nextLineLocation = currentLocation + fullLine.Length + 1;
|
||||
fullLine = fullLine.TrimEnd('\r');
|
||||
string trimmedLine = fullLine;
|
||||
|
||||
int leadingSpaces = trimmedLine.IndexOf('/');
|
||||
if (leadingSpaces != -1)
|
||||
{
|
||||
fullLine = fullLine.Substring(leadingSpaces);
|
||||
currentLocation += leadingSpaces;
|
||||
trimmedLine = trimmedLine.Substring(leadingSpaces + 3); // Remove leading spaces and the "///"
|
||||
trimmedLine = trimmedLine.Trim();
|
||||
|
||||
var span = Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(currentLocation, currentLocation + fullLine.Length);
|
||||
var location = Microsoft.CodeAnalysis.Location.Create(trivia.SyntaxTree, span);
|
||||
var commentType = CommentType.XmlDoc;
|
||||
cx.CommentGenerator.AddComment(Create(cx, location, commentType, trimmedLine, fullLine));
|
||||
}
|
||||
else
|
||||
{
|
||||
cx.ModelError("Unexpected comment format");
|
||||
}
|
||||
currentLocation = nextLineLocation;
|
||||
}
|
||||
break;
|
||||
|
||||
case SyntaxKind.SingleLineCommentTrivia:
|
||||
{
|
||||
string contents = trivia.ToString().Substring(2);
|
||||
var commentType = CommentType.Singleline;
|
||||
if (contents.Length > 0 && contents[0] == '/')
|
||||
{
|
||||
commentType = CommentType.XmlDoc;
|
||||
contents = contents.Substring(1); // An XML comment.
|
||||
}
|
||||
cx.CommentGenerator.AddComment(Create(cx, trivia.GetLocation(), commentType, contents.Trim(), trivia.ToFullString()));
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.MultiLineDocumentationCommentTrivia:
|
||||
case SyntaxKind.MultiLineCommentTrivia:
|
||||
/* We receive a single SyntaxTrivia for a multiline block spanning several lines.
|
||||
So we split it into separate lines
|
||||
*/
|
||||
text = trivia.ToFullString();
|
||||
|
||||
split = text.Split('\n');
|
||||
currentLocation = trivia.GetLocation().SourceSpan.Start;
|
||||
|
||||
for (int line = 0; line < split.Length; ++line)
|
||||
{
|
||||
string fullLine = split[line];
|
||||
var nextLineLocation = currentLocation + fullLine.Length + 1;
|
||||
fullLine = fullLine.TrimEnd('\r');
|
||||
string trimmedLine = fullLine;
|
||||
if (line == 0) trimmedLine = trimmedLine.Substring(2);
|
||||
if (line == split.Length - 1) trimmedLine = trimmedLine.Substring(0, trimmedLine.Length - 2);
|
||||
trimmedLine = trimmedLine.Trim();
|
||||
|
||||
var span = Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(currentLocation, currentLocation + fullLine.Length);
|
||||
var location = Microsoft.CodeAnalysis.Location.Create(trivia.SyntaxTree, span);
|
||||
var commentType = line == 0 ? CommentType.Multiline : CommentType.MultilineContinuation;
|
||||
cx.CommentGenerator.AddComment(Create(cx, location, commentType, trimmedLine, fullLine));
|
||||
currentLocation = nextLineLocation;
|
||||
}
|
||||
break;
|
||||
// Strangely, these are reported as SingleLineCommentTrivia.
|
||||
case SyntaxKind.DocumentationCommentExteriorTrivia:
|
||||
cx.ModelError("Unhandled comment type {0} for {1}", trivia.Kind(), trivia);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Extraction.Entities.Location location;
|
||||
|
||||
public override void Populate()
|
||||
{
|
||||
location = Context.Create(Location);
|
||||
Context.Emit(Tuples.commentline(this, Type == CommentType.MultilineContinuation ? CommentType.Multiline : Type, Text, RawText));
|
||||
Context.Emit(Tuples.commentline_location(this, location));
|
||||
}
|
||||
|
||||
public override Microsoft.CodeAnalysis.Location ReportingLocation => location.symbol;
|
||||
|
||||
public override bool NeedsPopulation => true;
|
||||
|
||||
public override IId Id
|
||||
{
|
||||
get
|
||||
{
|
||||
var loc = Context.Create(Location);
|
||||
return new Key(loc, ";commentline");
|
||||
}
|
||||
}
|
||||
|
||||
static CommentLine Create(Context cx, Microsoft.CodeAnalysis.Location loc, CommentType type, string text, string raw) => CommentLineFactory.Instance.CreateEntity(cx, loc, type, text, raw);
|
||||
|
||||
class CommentLineFactory : ICachedEntityFactory<(Microsoft.CodeAnalysis.Location, CommentType, string, string), CommentLine>
|
||||
{
|
||||
public static readonly CommentLineFactory Instance = new CommentLineFactory();
|
||||
|
||||
public CommentLine Create(Context cx, (Microsoft.CodeAnalysis.Location, CommentType, string, string) init) =>
|
||||
new CommentLine(cx, init.Item1, init.Item2, init.Item3, init.Item4);
|
||||
}
|
||||
|
||||
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.CSharp.Populators;
|
||||
using Semmle.Util;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Semmle.Extraction.Entities;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
public class Constructor : Method
|
||||
{
|
||||
Constructor(Context cx, IMethodSymbol init)
|
||||
: base(cx, init) { }
|
||||
|
||||
public override void Populate()
|
||||
{
|
||||
PopulateMethod();
|
||||
ExtractModifiers();
|
||||
ContainingType.ExtractGenerics();
|
||||
|
||||
Context.Emit(Tuples.constructors(this, symbol.ContainingType.Name, ContainingType, (Constructor)OriginalDefinition));
|
||||
Context.Emit(Tuples.constructor_location(this, Location));
|
||||
|
||||
if (symbol.IsImplicitlyDeclared)
|
||||
{
|
||||
var lineCounts = new LineCounts() { Total = 2, Code = 1, Comment = 0 };
|
||||
Context.Emit(Tuples.numlines(this, lineCounts));
|
||||
}
|
||||
ExtractCompilerGenerated();
|
||||
}
|
||||
|
||||
protected override void ExtractInitializers()
|
||||
{
|
||||
// Do not extract initializers for constructed types.
|
||||
if (!IsSourceDeclaration) return;
|
||||
|
||||
var syntax = Syntax;
|
||||
var initializer = syntax == null ? null : syntax.Initializer;
|
||||
|
||||
if (initializer == null) return;
|
||||
|
||||
Type initializerType;
|
||||
var symbolInfo = Context.GetSymbolInfo(initializer);
|
||||
|
||||
switch (initializer.Kind())
|
||||
{
|
||||
case SyntaxKind.BaseConstructorInitializer:
|
||||
initializerType = Type.Create(Context, symbol.ContainingType.BaseType);
|
||||
break;
|
||||
case SyntaxKind.ThisConstructorInitializer:
|
||||
initializerType = ContainingType;
|
||||
break;
|
||||
default:
|
||||
Context.ModelError(initializer, "Unknown initializer");
|
||||
return;
|
||||
}
|
||||
|
||||
var initInfo = new ExpressionInfo(Context,
|
||||
initializerType,
|
||||
Context.Create(initializer.ThisOrBaseKeyword.GetLocation()),
|
||||
Kinds.ExprKind.CONSTRUCTOR_INIT,
|
||||
this,
|
||||
-1,
|
||||
false,
|
||||
null);
|
||||
|
||||
var init = new Expression(initInfo);
|
||||
|
||||
var target = Constructor.Create(Context, (IMethodSymbol)symbolInfo.Symbol);
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
Context.ModelError(symbol, "Unable to resolve call");
|
||||
return;
|
||||
}
|
||||
|
||||
Context.Emit(Tuples.expr_call(init, target));
|
||||
|
||||
int child = 0;
|
||||
foreach (var arg in initializer.ArgumentList.Arguments)
|
||||
{
|
||||
Expression.Create(Context, arg.Expression, init, child++);
|
||||
}
|
||||
}
|
||||
|
||||
ConstructorDeclarationSyntax Syntax
|
||||
{
|
||||
get
|
||||
{
|
||||
return symbol.DeclaringSyntaxReferences.
|
||||
Select(r => r.GetSyntax()).
|
||||
OfType<ConstructorDeclarationSyntax>().
|
||||
FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
public new static Constructor Create(Context cx, IMethodSymbol constructor)
|
||||
{
|
||||
if (constructor == null) return null;
|
||||
|
||||
switch (constructor.MethodKind)
|
||||
{
|
||||
case MethodKind.StaticConstructor:
|
||||
case MethodKind.Constructor:
|
||||
return ConstructorFactory.Instance.CreateEntity(cx, constructor);
|
||||
default:
|
||||
throw new InternalError(constructor, "Attempt to create a Constructor from a symbol that isn't a constructor");
|
||||
}
|
||||
}
|
||||
|
||||
public override IId Id
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Key(tb =>
|
||||
{
|
||||
if (symbol.IsStatic) tb.Append("static");
|
||||
tb.Append(ContainingType);
|
||||
AddParametersToId(Context, tb, symbol);
|
||||
tb.Append("; constructor");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ConstructorDeclarationSyntax GetSyntax() =>
|
||||
symbol.DeclaringSyntaxReferences.Select(r => r.GetSyntax()).OfType<ConstructorDeclarationSyntax>().FirstOrDefault();
|
||||
|
||||
public override Microsoft.CodeAnalysis.Location FullLocation => ReportingLocation;
|
||||
|
||||
public override Microsoft.CodeAnalysis.Location ReportingLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
var syn = GetSyntax();
|
||||
if (syn != null)
|
||||
{
|
||||
return syn.Identifier.GetLocation();
|
||||
}
|
||||
else if (symbol.IsImplicitlyDeclared)
|
||||
{
|
||||
return ContainingType.ReportingLocation;
|
||||
}
|
||||
else
|
||||
{
|
||||
return symbol.ContainingType.Locations.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ConstructorFactory : ICachedEntityFactory<IMethodSymbol, Constructor>
|
||||
{
|
||||
public static readonly ConstructorFactory Instance = new ConstructorFactory();
|
||||
|
||||
public Constructor Create(Context cx, IMethodSymbol init) => new Constructor(cx, init);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.CSharp.Populators;
|
||||
using System.Linq;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
class Conversion : UserOperator
|
||||
{
|
||||
Conversion(Context cx, IMethodSymbol init)
|
||||
: base(cx, init) { }
|
||||
|
||||
public new static Conversion Create(Context cx, IMethodSymbol symbol) =>
|
||||
ConversionFactory.Instance.CreateEntity(cx, symbol);
|
||||
|
||||
public override Microsoft.CodeAnalysis.Location ReportingLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
return symbol.
|
||||
DeclaringSyntaxReferences.
|
||||
Select(r => r.GetSyntax()).
|
||||
OfType<ConversionOperatorDeclarationSyntax>().
|
||||
Select(s => s.FixedLocation()).
|
||||
Concat(symbol.Locations).
|
||||
FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
class ConversionFactory : ICachedEntityFactory<IMethodSymbol, Conversion>
|
||||
{
|
||||
public static readonly ConversionFactory Instance = new ConversionFactory();
|
||||
|
||||
public Conversion Create(Context cx, IMethodSymbol init) => new Conversion(cx, init);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
class Destructor : Method
|
||||
{
|
||||
Destructor(Context cx, IMethodSymbol init)
|
||||
: base(cx, init) { }
|
||||
|
||||
public override void Populate()
|
||||
{
|
||||
PopulateMethod();
|
||||
ExtractModifiers();
|
||||
ContainingType.ExtractGenerics();
|
||||
|
||||
Context.Emit(Tuples.destructors(this, string.Format("~{0}", symbol.ContainingType.Name), ContainingType, OriginalDefinition(Context, this, symbol)));
|
||||
Context.Emit(Tuples.destructor_location(this, Location));
|
||||
}
|
||||
|
||||
static new Destructor OriginalDefinition(Context cx, Destructor original, IMethodSymbol symbol)
|
||||
{
|
||||
return symbol.OriginalDefinition == null || Equals(symbol.OriginalDefinition, symbol) ? original : Create(cx, symbol.OriginalDefinition);
|
||||
}
|
||||
|
||||
public new static Destructor Create(Context cx, IMethodSymbol symbol) =>
|
||||
DestructorFactory.Instance.CreateEntity(cx, symbol);
|
||||
|
||||
class DestructorFactory : ICachedEntityFactory<IMethodSymbol, Destructor>
|
||||
{
|
||||
public static readonly DestructorFactory Instance = new DestructorFactory();
|
||||
|
||||
public Destructor Create(Context cx, IMethodSymbol init) => new Destructor(cx, init);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System.Linq;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
class Event : CachedSymbol<IEventSymbol>
|
||||
{
|
||||
Event(Context cx, IEventSymbol init)
|
||||
: base(cx, init) { }
|
||||
|
||||
public override IId Id
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Key(tb =>
|
||||
{
|
||||
tb.Append(ContainingType);
|
||||
tb.Append(".");
|
||||
Method.AddExplicitInterfaceQualifierToId(Context, tb, symbol.ExplicitInterfaceImplementations);
|
||||
tb.Append(symbol.Name);
|
||||
tb.Append(";event");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public override void Populate()
|
||||
{
|
||||
var type = Type.Create(Context, symbol.Type);
|
||||
Context.Emit(Tuples.events(this, symbol.GetName(), ContainingType, type.TypeRef, Create(Context, symbol.OriginalDefinition)));
|
||||
|
||||
var adder = symbol.AddMethod;
|
||||
if (adder != null)
|
||||
EventAccessor.Create(Context, adder);
|
||||
|
||||
var remover = symbol.RemoveMethod;
|
||||
if (remover != null)
|
||||
EventAccessor.Create(Context, remover);
|
||||
|
||||
ExtractModifiers();
|
||||
BindComments();
|
||||
|
||||
var declSyntaxReferences = IsSourceDeclaration
|
||||
? symbol.DeclaringSyntaxReferences.Select(d => d.GetSyntax()).ToArray()
|
||||
: Enumerable.Empty<SyntaxNode>();
|
||||
|
||||
foreach (var explicitInterface in symbol.ExplicitInterfaceImplementations.Select(impl => Type.Create(Context, impl.ContainingType)))
|
||||
{
|
||||
Context.Emit(Tuples.explicitly_implements(this, explicitInterface.TypeRef));
|
||||
|
||||
foreach (var syntax in declSyntaxReferences.OfType<EventDeclarationSyntax>())
|
||||
TypeMention.Create(Context, syntax.ExplicitInterfaceSpecifier.Name, this, explicitInterface);
|
||||
}
|
||||
|
||||
foreach (var l in Locations)
|
||||
Context.Emit(Tuples.event_location(this, l));
|
||||
|
||||
foreach (var syntaxType in declSyntaxReferences.OfType<VariableDeclaratorSyntax>().
|
||||
Select(d => d.Parent).
|
||||
OfType<VariableDeclarationSyntax>().
|
||||
Select(syntax => syntax.Type))
|
||||
TypeMention.Create(Context, syntaxType, this, type);
|
||||
}
|
||||
|
||||
public static Event Create(Context cx, IEventSymbol symbol) => EventFactory.Instance.CreateEntity(cx, symbol);
|
||||
|
||||
class EventFactory : ICachedEntityFactory<IEventSymbol, Event>
|
||||
{
|
||||
public static readonly EventFactory Instance = new EventFactory();
|
||||
|
||||
public Event Create(Context cx, IEventSymbol init) => new Event(cx, init);
|
||||
}
|
||||
|
||||
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.NoLabel;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
class EventAccessor : Accessor
|
||||
{
|
||||
EventAccessor(Context cx, IMethodSymbol init)
|
||||
: base(cx, init) { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event symbol associated with this accessor.
|
||||
/// </summary>
|
||||
IEventSymbol EventSymbol => symbol.AssociatedSymbol as IEventSymbol;
|
||||
|
||||
public override void Populate()
|
||||
{
|
||||
PopulateMethod();
|
||||
ContainingType.ExtractGenerics();
|
||||
|
||||
var @event = EventSymbol;
|
||||
if (@event == null)
|
||||
{
|
||||
Context.ModelError(symbol, "Unhandled event accessor associated symbol");
|
||||
return;
|
||||
}
|
||||
|
||||
var parent = Event.Create(Context, @event);
|
||||
int kind;
|
||||
EventAccessor unboundAccessor;
|
||||
if (symbol.Equals(@event.AddMethod))
|
||||
{
|
||||
kind = 1;
|
||||
unboundAccessor = Create(Context, @event.OriginalDefinition.AddMethod);
|
||||
}
|
||||
else if (symbol.Equals(@event.RemoveMethod))
|
||||
{
|
||||
kind = 2;
|
||||
unboundAccessor = Create(Context, @event.OriginalDefinition.RemoveMethod);
|
||||
}
|
||||
else
|
||||
{
|
||||
Context.ModelError(symbol, "Undhandled event accessor kind");
|
||||
return;
|
||||
}
|
||||
|
||||
Context.Emit(Tuples.event_accessors(this, kind, symbol.Name, parent, unboundAccessor));
|
||||
|
||||
foreach (var l in Locations)
|
||||
Context.Emit(Tuples.event_accessor_location(this, l));
|
||||
|
||||
Overrides();
|
||||
}
|
||||
|
||||
public new static EventAccessor Create(Context cx, IMethodSymbol symbol) =>
|
||||
EventAccessorFactory.Instance.CreateEntity(cx, symbol);
|
||||
|
||||
class EventAccessorFactory : ICachedEntityFactory<IMethodSymbol, EventAccessor>
|
||||
{
|
||||
public static readonly EventAccessorFactory Instance = new EventAccessorFactory();
|
||||
|
||||
public EventAccessor Create(Context cx, IMethodSymbol init) => new EventAccessor(cx, init);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,527 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.CSharp.Populators;
|
||||
using Semmle.Extraction.Entities;
|
||||
using Semmle.Extraction.Kinds;
|
||||
using System.Linq;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
public interface IExpressionParentEntity : IEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether this entity is the parent of a top-level expression.
|
||||
/// </summary>
|
||||
bool IsTopLevelParent { get; }
|
||||
}
|
||||
|
||||
class Expression : FreshEntity, IExpressionParentEntity
|
||||
{
|
||||
public readonly Type Type;
|
||||
public readonly Extraction.Entities.Location Location;
|
||||
public readonly ExprKind Kind;
|
||||
|
||||
internal Expression(IExpressionInfo info)
|
||||
: base(info.Context)
|
||||
{
|
||||
Location = info.Location;
|
||||
Kind = info.Kind;
|
||||
Type = info.Type ?? NullType.Create(cx);
|
||||
|
||||
cx.Emit(Tuples.expressions(this, Kind, Type.TypeRef));
|
||||
if (info.Parent.IsTopLevelParent)
|
||||
cx.Emit(Tuples.expr_parent_top_level(this, info.Child, info.Parent));
|
||||
else
|
||||
cx.Emit(Tuples.expr_parent(this, info.Child, info.Parent));
|
||||
cx.Emit(Tuples.expr_location(this, Location));
|
||||
|
||||
if (info.IsCompilerGenerated)
|
||||
cx.Emit(Tuples.expr_compiler_generated(this));
|
||||
|
||||
if (info.ExprValue is string value)
|
||||
cx.Emit(Tuples.expr_value(this, value));
|
||||
|
||||
Type.ExtractGenerics();
|
||||
}
|
||||
|
||||
public override Microsoft.CodeAnalysis.Location ReportingLocation => Location.symbol;
|
||||
|
||||
bool IExpressionParentEntity.IsTopLevelParent => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string represention of a constant value.
|
||||
/// </summary>
|
||||
/// <param name="obj">The value.</param>
|
||||
/// <returns>The string representation.</returns>
|
||||
public static string ValueAsString(object value)
|
||||
{
|
||||
return value == null ? "null" : value is bool ? ((bool)value ? "true" : "false") : value.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an expression from a syntax node.
|
||||
/// Inserts type conversion as required.
|
||||
/// </summary>
|
||||
/// <param name="cx">The extraction context.</param>
|
||||
/// <param name="node">The node to extract.</param>
|
||||
/// <param name="parent">The parent entity.</param>
|
||||
/// <param name="child">The child index.</param>
|
||||
/// <param name="type">A type hint.</param>
|
||||
/// <returns>The new expression.</returns>
|
||||
public static Expression Create(Context cx, ExpressionSyntax node, IExpressionParentEntity parent, int child) =>
|
||||
CreateFromNode(new ExpressionNodeInfo(cx, node, parent, child));
|
||||
|
||||
public static Expression CreateFromNode(ExpressionNodeInfo info) => Expressions.ImplicitCast.Create(info);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an expression from a syntax node.
|
||||
/// Inserts type conversion as required.
|
||||
/// Population is deferred to avoid overflowing the stack.
|
||||
/// </summary>
|
||||
/// <param name="cx">The extraction context.</param>
|
||||
/// <param name="node">The node to extract.</param>
|
||||
/// <param name="parent">The parent entity.</param>
|
||||
/// <param name="child">The child index.</param>
|
||||
/// <param name="type">A type hint.</param>
|
||||
public static void CreateDeferred(Context cx, ExpressionSyntax node, IExpressionParentEntity parent, int child)
|
||||
{
|
||||
if (ContainsPattern(node))
|
||||
// Expressions with patterns should be created right away, as they may introduce
|
||||
// local variables referenced in `LocalVariable::GetAlreadyCreated()`
|
||||
Create(cx, node, parent, child);
|
||||
else
|
||||
cx.PopulateLater(() => Create(cx, node, parent, child));
|
||||
}
|
||||
|
||||
static bool ContainsPattern(SyntaxNode node) =>
|
||||
node is PatternSyntax || node is VariableDesignationSyntax || node.ChildNodes().Any(ContainsPattern);
|
||||
|
||||
/// <summary>
|
||||
/// Adapt the operator kind depending on whether it's a dynamic call or a user-operator call.
|
||||
/// </summary>
|
||||
/// <param name="cx"></param>
|
||||
/// <param name="node"></param>
|
||||
/// <param name="originalKind"></param>
|
||||
/// <returns></returns>
|
||||
public static ExprKind UnaryOperatorKind(Context cx, ExprKind originalKind, ExpressionSyntax node) =>
|
||||
GetCallType(cx, node).AdjustKind(originalKind);
|
||||
|
||||
/// <summary>
|
||||
/// If the expression calls an operator, add an expr_call()
|
||||
/// to show the target of the call. Also note the dynamic method
|
||||
/// name if available.
|
||||
/// </summary>
|
||||
/// <param name="cx">Context</param>
|
||||
/// <param name="node">The expression.</param>
|
||||
public void OperatorCall(ExpressionSyntax node)
|
||||
{
|
||||
var @operator = cx.GetSymbolInfo(node);
|
||||
var method = @operator.Symbol as IMethodSymbol;
|
||||
|
||||
if (GetCallType(cx, node) == CallType.Dynamic)
|
||||
{
|
||||
UserOperator.OperatorSymbol(method.Name, out string operatorName);
|
||||
cx.Emit(Tuples.dynamic_member_name(this, operatorName));
|
||||
return;
|
||||
}
|
||||
|
||||
if (method != null)
|
||||
cx.Emit(Tuples.expr_call(this, Method.Create(cx, method)));
|
||||
}
|
||||
|
||||
public enum CallType
|
||||
{
|
||||
None,
|
||||
BuiltInOperator,
|
||||
Dynamic,
|
||||
UserOperator
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine what type of method was called for this expression.
|
||||
/// </summary>
|
||||
/// <param name="cx">The context.</param>
|
||||
/// <param name="node">The expression</param>
|
||||
/// <returns>The call type.</returns>
|
||||
public static CallType GetCallType(Context cx, ExpressionSyntax node)
|
||||
{
|
||||
var @operator = cx.GetSymbolInfo(node);
|
||||
|
||||
if (@operator.Symbol != null)
|
||||
{
|
||||
var method = @operator.Symbol as IMethodSymbol;
|
||||
|
||||
var containingSymbol = method.ContainingSymbol as ITypeSymbol;
|
||||
if (containingSymbol != null && containingSymbol.TypeKind == Microsoft.CodeAnalysis.TypeKind.Dynamic)
|
||||
{
|
||||
return CallType.Dynamic;
|
||||
}
|
||||
|
||||
switch (method.MethodKind)
|
||||
{
|
||||
case MethodKind.BuiltinOperator:
|
||||
if (method.ContainingType != null && method.ContainingType.TypeKind == Microsoft.CodeAnalysis.TypeKind.Delegate)
|
||||
return CallType.UserOperator;
|
||||
return CallType.BuiltInOperator;
|
||||
default:
|
||||
return CallType.UserOperator;
|
||||
}
|
||||
}
|
||||
|
||||
return CallType.None;
|
||||
}
|
||||
|
||||
|
||||
public static bool IsDynamic(Context cx, ExpressionSyntax node)
|
||||
{
|
||||
var ti = cx.GetTypeInfo(node).ConvertedType;
|
||||
return ti != null && ti.TypeKind == Microsoft.CodeAnalysis.TypeKind.Dynamic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given b in a?.b.c, return a.
|
||||
/// </summary>
|
||||
/// <param name="node">A MemberBindingExpression.</param>
|
||||
/// <returns>The qualifier of the conditional access.</returns>
|
||||
protected static ExpressionSyntax FindConditionalQualifier(ExpressionSyntax node)
|
||||
{
|
||||
for (SyntaxNode n = node; n != null; n = n.Parent)
|
||||
{
|
||||
var conditionalAccess = n.Parent as ConditionalAccessExpressionSyntax;
|
||||
|
||||
if (conditionalAccess != null && conditionalAccess.WhenNotNull == n)
|
||||
return conditionalAccess.Expression;
|
||||
}
|
||||
|
||||
throw new InternalError(node, "Unable to locate a ConditionalAccessExpression");
|
||||
}
|
||||
|
||||
public void MakeConditional()
|
||||
{
|
||||
cx.Emit(Tuples.conditional_access(this));
|
||||
}
|
||||
|
||||
public void PopulateArguments(Context cx, BaseArgumentListSyntax args, int child)
|
||||
{
|
||||
foreach (var arg in args.Arguments)
|
||||
PopulateArgument(cx, arg, child++);
|
||||
}
|
||||
|
||||
private void PopulateArgument(Context cx, ArgumentSyntax arg, int child)
|
||||
{
|
||||
var expr = Create(cx, arg.Expression, this, child);
|
||||
int mode;
|
||||
switch (arg.RefOrOutKeyword.Kind())
|
||||
{
|
||||
case SyntaxKind.RefKeyword:
|
||||
mode = 1;
|
||||
break;
|
||||
case SyntaxKind.OutKeyword:
|
||||
mode = 2;
|
||||
break;
|
||||
case SyntaxKind.None:
|
||||
mode = 0;
|
||||
break;
|
||||
default:
|
||||
throw new InternalError(arg, "Unknown argument type");
|
||||
}
|
||||
cx.Emit(Tuples.expr_argument(expr, mode));
|
||||
|
||||
if (arg.NameColon != null)
|
||||
{
|
||||
cx.Emit(Tuples.expr_argument_name(expr, arg.NameColon.Name.Identifier.Text));
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => Label.ToString();
|
||||
|
||||
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel;
|
||||
}
|
||||
|
||||
static class CallTypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adjust the expression kind <paramref name="k"/> to match this call type.
|
||||
/// </summary>
|
||||
public static ExprKind AdjustKind(this Expression.CallType ct, ExprKind k)
|
||||
{
|
||||
switch (ct)
|
||||
{
|
||||
case Expression.CallType.Dynamic:
|
||||
case Expression.CallType.UserOperator:
|
||||
return ExprKind.OPERATOR_INVOCATION;
|
||||
default:
|
||||
return k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Expression<SyntaxNode> : Expression
|
||||
where SyntaxNode : ExpressionSyntax
|
||||
{
|
||||
public readonly SyntaxNode Syntax;
|
||||
|
||||
protected Expression(ExpressionNodeInfo info)
|
||||
: base(info)
|
||||
{
|
||||
Syntax = (SyntaxNode)info.Node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates expression-type specific relations in the trap file. The general relations
|
||||
/// <code>expressions</code> and <code>expr_location</code> are populated by the constructor
|
||||
/// (should not fail), so even if expression-type specific population fails (e.g., in
|
||||
/// standalone extraction), the expression created via
|
||||
/// <see cref="Expression.Create(Context, ExpressionSyntax, IEntity, int, ITypeSymbol)"/> will
|
||||
/// still be valid.
|
||||
/// </summary>
|
||||
protected abstract void Populate();
|
||||
|
||||
protected Expression TryPopulate()
|
||||
{
|
||||
cx.Try(Syntax, null, Populate);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds all information required to create an Expression entity.
|
||||
/// </summary>
|
||||
interface IExpressionInfo
|
||||
{
|
||||
Context Context { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the expression.
|
||||
/// </summary>
|
||||
Type Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The location of the expression.
|
||||
/// </summary>
|
||||
Extraction.Entities.Location Location { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The kind of the expression.
|
||||
/// </summary>
|
||||
ExprKind Kind { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The parent of the expression.
|
||||
/// </summary>
|
||||
IExpressionParentEntity Parent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The child index of the expression.
|
||||
/// </summary>
|
||||
int Child { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds if this is an implicit expression.
|
||||
/// </summary>
|
||||
bool IsCompilerGenerated { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string representation of the value.
|
||||
/// null is encoded as the string "null".
|
||||
/// If the expression does not have a value, then this
|
||||
/// is null.
|
||||
/// </summary>
|
||||
string ExprValue { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly constructed expression information.
|
||||
/// </summary>
|
||||
class ExpressionInfo : IExpressionInfo
|
||||
{
|
||||
public Context Context { get; }
|
||||
public Type Type { get; }
|
||||
public Extraction.Entities.Location Location { get; }
|
||||
public ExprKind Kind { get; }
|
||||
public IExpressionParentEntity Parent { get; }
|
||||
public int Child { get; }
|
||||
public bool IsCompilerGenerated { get; }
|
||||
public string ExprValue { get; }
|
||||
|
||||
public ExpressionInfo(Context cx, Type type, Extraction.Entities.Location location, ExprKind kind, IExpressionParentEntity parent, int child, bool isCompilerGenerated, string value)
|
||||
{
|
||||
Context = cx;
|
||||
Type = type;
|
||||
Location = location;
|
||||
Kind = kind;
|
||||
Parent = parent;
|
||||
Child = child;
|
||||
ExprValue = value;
|
||||
IsCompilerGenerated = isCompilerGenerated;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expression information constructed from a syntax node.
|
||||
/// </summary>
|
||||
class ExpressionNodeInfo : IExpressionInfo
|
||||
{
|
||||
public ExpressionNodeInfo(Context cx, ExpressionSyntax node, IExpressionParentEntity parent, int child) :
|
||||
this(cx, node, parent, child, cx.GetTypeInfo(node))
|
||||
{
|
||||
}
|
||||
|
||||
public ExpressionNodeInfo(Context cx, ExpressionSyntax node, IExpressionParentEntity parent, int child, TypeInfo typeInfo)
|
||||
{
|
||||
Context = cx;
|
||||
Node = node;
|
||||
Parent = parent;
|
||||
Child = child;
|
||||
TypeInfo = typeInfo;
|
||||
Conversion = cx.Model(node).GetConversion(node);
|
||||
}
|
||||
|
||||
public ExpressionNodeInfo(Context cx, ExpressionSyntax node, IExpressionParentEntity parent, int child, ITypeSymbol type) :
|
||||
this(cx, node, parent, child)
|
||||
{
|
||||
Type = Type.Create(cx, type);
|
||||
}
|
||||
|
||||
public Context Context { get; }
|
||||
public ExpressionSyntax Node { get; private set; }
|
||||
public IExpressionParentEntity Parent { get; set; }
|
||||
public int Child { get; set; }
|
||||
public TypeInfo TypeInfo { get; }
|
||||
public Microsoft.CodeAnalysis.CSharp.Conversion Conversion { get; }
|
||||
|
||||
public ITypeSymbol ResolvedType => Context.DisambiguateType(TypeInfo.Type);
|
||||
public ITypeSymbol ConvertedType => Context.DisambiguateType(TypeInfo.ConvertedType);
|
||||
|
||||
public ITypeSymbol ExpressionType
|
||||
{
|
||||
get
|
||||
{
|
||||
var type = ResolvedType;
|
||||
|
||||
if (type == null)
|
||||
type = Context.DisambiguateType(TypeInfo.Type ?? TypeInfo.ConvertedType);
|
||||
|
||||
// Roslyn workaround: It can't work out the type of "new object[0]"
|
||||
// Clearly a bug.
|
||||
if (type != null && type.TypeKind == Microsoft.CodeAnalysis.TypeKind.Error)
|
||||
{
|
||||
var arrayCreation = Node as ArrayCreationExpressionSyntax;
|
||||
if (arrayCreation != null)
|
||||
{
|
||||
var elementType = Context.GetType(arrayCreation.Type.ElementType);
|
||||
|
||||
if (elementType != null)
|
||||
return Context.Compilation.CreateArrayTypeSymbol(elementType, arrayCreation.Type.RankSpecifiers.Count);
|
||||
}
|
||||
|
||||
Context.ModelError(Node, "Failed to determine type");
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
Microsoft.CodeAnalysis.Location location;
|
||||
|
||||
public Microsoft.CodeAnalysis.Location CodeAnalysisLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (location == null)
|
||||
location = Node.FixedLocation();
|
||||
return location;
|
||||
}
|
||||
set
|
||||
{
|
||||
location = value;
|
||||
}
|
||||
}
|
||||
|
||||
public SemanticModel Model => Context.Model(Node);
|
||||
|
||||
public string ExprValue
|
||||
{
|
||||
get
|
||||
{
|
||||
var c = Model.GetConstantValue(Node);
|
||||
return c.HasValue ? Expression.ValueAsString(c.Value) : null;
|
||||
}
|
||||
}
|
||||
|
||||
Type cachedType;
|
||||
|
||||
public Type Type
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cachedType == null)
|
||||
cachedType = Type.Create(Context, ExpressionType);
|
||||
return cachedType;
|
||||
}
|
||||
set
|
||||
{
|
||||
cachedType = value;
|
||||
}
|
||||
}
|
||||
|
||||
Extraction.Entities.Location cachedLocation;
|
||||
|
||||
public Extraction.Entities.Location Location
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cachedLocation == null)
|
||||
cachedLocation = Context.Create(CodeAnalysisLocation);
|
||||
return cachedLocation;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
cachedLocation = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ExprKind Kind { get; set; } = ExprKind.UNKNOWN;
|
||||
|
||||
public bool IsCompilerGenerated { get; set; }
|
||||
|
||||
public ExpressionNodeInfo SetParent(IExpressionParentEntity parent, int child)
|
||||
{
|
||||
Parent = parent;
|
||||
Child = child;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpressionNodeInfo SetKind(ExprKind kind)
|
||||
{
|
||||
Kind = kind;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpressionNodeInfo SetType(Type type)
|
||||
{
|
||||
Type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpressionNodeInfo SetNode(ExpressionSyntax node)
|
||||
{
|
||||
Node = node;
|
||||
return this;
|
||||
}
|
||||
|
||||
SymbolInfo cachedSymbolInfo;
|
||||
|
||||
public SymbolInfo SymbolInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cachedSymbolInfo.Symbol == null && cachedSymbolInfo.CandidateReason == CandidateReason.None)
|
||||
cachedSymbolInfo = Model.GetSymbolInfo(Node);
|
||||
return cachedSymbolInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.Kinds;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
class Access : Expression
|
||||
{
|
||||
static ExprKind AccessKind(Context cx, ISymbol symbol)
|
||||
{
|
||||
switch (symbol.Kind)
|
||||
{
|
||||
case SymbolKind.TypeParameter:
|
||||
case SymbolKind.NamedType:
|
||||
return ExprKind.TYPE_ACCESS;
|
||||
|
||||
case SymbolKind.Field:
|
||||
return ExprKind.FIELD_ACCESS;
|
||||
|
||||
case SymbolKind.Property:
|
||||
return ExprKind.PROPERTY_ACCESS;
|
||||
|
||||
case SymbolKind.Event:
|
||||
return ExprKind.EVENT_ACCESS;
|
||||
|
||||
case SymbolKind.Method:
|
||||
return ExprKind.METHOD_ACCESS;
|
||||
|
||||
case SymbolKind.Local:
|
||||
case SymbolKind.RangeVariable:
|
||||
return ExprKind.LOCAL_VARIABLE_ACCESS;
|
||||
|
||||
case SymbolKind.Parameter:
|
||||
return ExprKind.PARAMETER_ACCESS;
|
||||
|
||||
default:
|
||||
cx.ModelError(symbol, "Unhandled access kind '{0}'", symbol.Kind);
|
||||
return ExprKind.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
Access(ExpressionNodeInfo info, ISymbol symbol, bool implicitThis, IEntity target)
|
||||
: base(info.SetKind(AccessKind(info.Context, symbol)))
|
||||
{
|
||||
cx.Emit(Tuples.expr_access(this, target));
|
||||
|
||||
if (implicitThis && !symbol.IsStatic)
|
||||
{
|
||||
This.CreateImplicit(cx, Type.Create(cx, symbol.ContainingType), Location, this, -1);
|
||||
}
|
||||
}
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info, ISymbol symbol, bool implicitThis, IEntity target) => new Access(info, symbol, implicitThis, target);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using Semmle.Extraction.Kinds;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
class ArgList : Expression<ExpressionSyntax>
|
||||
{
|
||||
ArgList(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.UNKNOWN)) { }
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static ArgList Create(ExpressionNodeInfo info) => new ArgList(info);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.Kinds;
|
||||
using System.Linq;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
abstract class ArrayCreation<SyntaxNode> : Expression<SyntaxNode> where SyntaxNode : ExpressionSyntax
|
||||
{
|
||||
protected ArrayCreation(ExpressionNodeInfo info) : base(info) { }
|
||||
}
|
||||
|
||||
class StackAllocArrayCreation : ArrayCreation<StackAllocArrayCreationExpressionSyntax>
|
||||
{
|
||||
StackAllocArrayCreation(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.ARRAY_CREATION)) { }
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new StackAllocArrayCreation(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
var arrayType = Syntax.Type as ArrayTypeSyntax;
|
||||
|
||||
if (arrayType == null)
|
||||
{
|
||||
cx.ModelError(Syntax, "Unexpected array type");
|
||||
return;
|
||||
}
|
||||
|
||||
var child = 0;
|
||||
|
||||
foreach (var rank in arrayType.RankSpecifiers.SelectMany(rs => rs.Sizes))
|
||||
{
|
||||
Create(cx, rank, this, child++);
|
||||
}
|
||||
|
||||
cx.Emit(Tuples.explicitly_sized_array_creation(this));
|
||||
}
|
||||
}
|
||||
|
||||
class ExplicitArrayCreation : ArrayCreation<ArrayCreationExpressionSyntax>
|
||||
{
|
||||
ExplicitArrayCreation(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.ARRAY_CREATION)) { }
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new ExplicitArrayCreation(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
var child = 0;
|
||||
bool explicitlySized = false;
|
||||
|
||||
foreach (var rank in Syntax.Type.RankSpecifiers.SelectMany(rs => rs.Sizes))
|
||||
{
|
||||
if (rank is OmittedArraySizeExpressionSyntax)
|
||||
{
|
||||
// Create an expression which simulates the explicit size of the array
|
||||
|
||||
if (Syntax.Initializer != null)
|
||||
{
|
||||
// An implicitly-sized array must have an initializer.
|
||||
// Guard it just in case.
|
||||
var size = Syntax.Initializer.Expressions.Count;
|
||||
|
||||
var info = new ExpressionInfo(
|
||||
cx,
|
||||
Type.Create(cx, cx.Compilation.GetSpecialType(Microsoft.CodeAnalysis.SpecialType.System_Int32)),
|
||||
Location,
|
||||
ExprKind.INT_LITERAL,
|
||||
this,
|
||||
child,
|
||||
false,
|
||||
size.ToString());
|
||||
|
||||
new Expression(info);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Create(cx, rank, this, child);
|
||||
explicitlySized = true;
|
||||
}
|
||||
child++;
|
||||
}
|
||||
if (Syntax.Initializer != null)
|
||||
{
|
||||
ArrayInitializer.Create(new ExpressionNodeInfo(cx, Syntax.Initializer, this, -1));
|
||||
}
|
||||
|
||||
if (explicitlySized)
|
||||
cx.Emit(Tuples.explicitly_sized_array_creation(this));
|
||||
}
|
||||
}
|
||||
|
||||
class ImplicitArrayCreation : ArrayCreation<ImplicitArrayCreationExpressionSyntax>
|
||||
{
|
||||
ImplicitArrayCreation(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.ARRAY_CREATION)) { }
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new ImplicitArrayCreation(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
if (Syntax.Initializer != null)
|
||||
{
|
||||
ArrayInitializer.Create(new ExpressionNodeInfo(cx, Syntax.Initializer, this, -1));
|
||||
}
|
||||
|
||||
cx.Emit(Tuples.implicitly_typed_array_creation(this));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Semmle.Extraction.CSharp.Populators;
|
||||
using Semmle.Extraction.Kinds;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
class Assignment : Expression<AssignmentExpressionSyntax>
|
||||
{
|
||||
Assignment(ExpressionNodeInfo info)
|
||||
: base(info.SetKind(GetKind(info.Context, (AssignmentExpressionSyntax)info.Node)))
|
||||
{
|
||||
}
|
||||
|
||||
public static Assignment Create(ExpressionNodeInfo info)
|
||||
{
|
||||
var ret = new Assignment(info);
|
||||
ret.TryPopulate();
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
var operatorKind = OperatorKind;
|
||||
if (operatorKind.HasValue)
|
||||
{
|
||||
// Convert assignment such as `a += b` into `a = a + b`.
|
||||
var simpleAssignExpr = new Expression(new ExpressionInfo(cx, Type, Location, ExprKind.SIMPLE_ASSIGN, this, 2, false, null));
|
||||
Create(cx, Syntax.Left, simpleAssignExpr, 1);
|
||||
var opexpr = new Expression(new ExpressionInfo(cx, Type, Location, operatorKind.Value, simpleAssignExpr, 0, false, null));
|
||||
Create(cx, Syntax.Left, opexpr, 0);
|
||||
Create(cx, Syntax.Right, opexpr, 1);
|
||||
opexpr.OperatorCall(Syntax);
|
||||
}
|
||||
else
|
||||
{
|
||||
Create(cx, Syntax.Left, this, 1);
|
||||
Create(cx, Syntax.Right, this, 0);
|
||||
|
||||
if (Kind == ExprKind.ADD_EVENT || Kind == ExprKind.REMOVE_EVENT)
|
||||
{
|
||||
OperatorCall(Syntax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ExprKind GetAssignmentOperation(Context cx, AssignmentExpressionSyntax syntax)
|
||||
{
|
||||
switch (syntax.OperatorToken.Kind())
|
||||
{
|
||||
case SyntaxKind.PlusEqualsToken:
|
||||
return ExprKind.ASSIGN_ADD;
|
||||
case SyntaxKind.MinusEqualsToken:
|
||||
return ExprKind.ASSIGN_SUB;
|
||||
case SyntaxKind.EqualsToken:
|
||||
return ExprKind.SIMPLE_ASSIGN;
|
||||
case SyntaxKind.BarEqualsToken:
|
||||
return ExprKind.ASSIGN_OR;
|
||||
case SyntaxKind.AmpersandEqualsToken:
|
||||
return ExprKind.ASSIGN_AND;
|
||||
case SyntaxKind.CaretEqualsToken:
|
||||
return ExprKind.ASSIGN_XOR;
|
||||
case SyntaxKind.AsteriskEqualsToken:
|
||||
return ExprKind.ASSIGN_MUL;
|
||||
case SyntaxKind.PercentEqualsToken:
|
||||
return ExprKind.ASSIGN_REM;
|
||||
case SyntaxKind.SlashEqualsToken:
|
||||
return ExprKind.ASSIGN_DIV;
|
||||
case SyntaxKind.LessThanLessThanEqualsToken:
|
||||
return ExprKind.ASSIGN_LSHIFT;
|
||||
case SyntaxKind.GreaterThanGreaterThanEqualsToken:
|
||||
return ExprKind.ASSIGN_RSHIFT;
|
||||
default:
|
||||
cx.ModelError(syntax, "Unrecognised assignment type " + GetKind(cx, syntax));
|
||||
return ExprKind.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static ExprKind GetKind(Context cx, AssignmentExpressionSyntax syntax)
|
||||
{
|
||||
var leftSymbol = cx.GetSymbolInfo(syntax.Left);
|
||||
bool assignEvent = leftSymbol.Symbol != null && leftSymbol.Symbol is IEventSymbol;
|
||||
var kind = GetAssignmentOperation(cx, syntax);
|
||||
var leftType = cx.GetType(syntax.Left);
|
||||
|
||||
if (leftType != null && leftType.SpecialType != SpecialType.None)
|
||||
{
|
||||
// In Mono, the builtin types did not specify their operator invocation
|
||||
// even though EVERY operator has an invocation in C#. (This is a flaw in the dbscheme and should be fixed).
|
||||
return kind;
|
||||
}
|
||||
|
||||
if (kind == ExprKind.ASSIGN_ADD && assignEvent)
|
||||
{
|
||||
return ExprKind.ADD_EVENT;
|
||||
}
|
||||
|
||||
if (kind == ExprKind.ASSIGN_SUB && assignEvent)
|
||||
{
|
||||
return ExprKind.REMOVE_EVENT;
|
||||
}
|
||||
|
||||
return kind;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the kind of this assignment operator (<code>null</code> if the
|
||||
/// assignment is not an assignment operator). For example, the operator
|
||||
/// kind of `*=` is `*`.
|
||||
/// </summary>
|
||||
ExprKind? OperatorKind
|
||||
{
|
||||
get
|
||||
{
|
||||
var kind = Kind;
|
||||
if (kind == ExprKind.REMOVE_EVENT || kind == ExprKind.ADD_EVENT || kind == ExprKind.SIMPLE_ASSIGN)
|
||||
return null;
|
||||
|
||||
if (CallType.AdjustKind(kind) == ExprKind.OPERATOR_INVOCATION)
|
||||
return ExprKind.OPERATOR_INVOCATION;
|
||||
|
||||
switch (kind)
|
||||
{
|
||||
case ExprKind.ASSIGN_ADD:
|
||||
return ExprKind.ADD;
|
||||
case ExprKind.ASSIGN_AND:
|
||||
return ExprKind.BIT_AND;
|
||||
case ExprKind.ASSIGN_DIV:
|
||||
return ExprKind.DIV;
|
||||
case ExprKind.ASSIGN_LSHIFT:
|
||||
return ExprKind.LSHIFT;
|
||||
case ExprKind.ASSIGN_MUL:
|
||||
return ExprKind.MUL;
|
||||
case ExprKind.ASSIGN_OR:
|
||||
return ExprKind.BIT_OR;
|
||||
case ExprKind.ASSIGN_REM:
|
||||
return ExprKind.REM;
|
||||
case ExprKind.ASSIGN_RSHIFT:
|
||||
return ExprKind.RSHIFT;
|
||||
case ExprKind.ASSIGN_SUB:
|
||||
return ExprKind.SUB;
|
||||
case ExprKind.ASSIGN_XOR:
|
||||
return ExprKind.BIT_XOR;
|
||||
default:
|
||||
cx.ModelError(Syntax, "Couldn't unfold assignment of type " + kind);
|
||||
return ExprKind.UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public new CallType CallType => GetCallType(cx, Syntax);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.Kinds;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
class Await : Expression<AwaitExpressionSyntax>
|
||||
{
|
||||
Await(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.AWAIT)) { }
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new Await(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
Create(cx, Syntax.Expression, this, 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.Kinds;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
class Base : Expression
|
||||
{
|
||||
Base(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.BASE_ACCESS)) { }
|
||||
|
||||
public static Base Create(ExpressionNodeInfo info) => new Base(info);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.Kinds;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
class Binary : Expression<BinaryExpressionSyntax>
|
||||
{
|
||||
Binary(ExpressionNodeInfo info)
|
||||
: base(info.SetKind(GetKind(info.Context, (BinaryExpressionSyntax)info.Node)))
|
||||
{
|
||||
}
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new Binary(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
OperatorCall(Syntax);
|
||||
CreateDeferred(cx, Syntax.Left, this, 0);
|
||||
CreateDeferred(cx, Syntax.Right, this, 1);
|
||||
}
|
||||
|
||||
static ExprKind GetKind(Context cx, BinaryExpressionSyntax node)
|
||||
{
|
||||
var k = GetBinaryTokenKind(cx, node.OperatorToken.Kind());
|
||||
return GetCallType(cx, node).AdjustKind(k);
|
||||
}
|
||||
|
||||
static ExprKind GetBinaryTokenKind(Context cx, SyntaxKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case SyntaxKind.LessThanToken: return ExprKind.LT;
|
||||
case SyntaxKind.PlusToken: return ExprKind.ADD;
|
||||
case SyntaxKind.LessThanEqualsToken: return ExprKind.LE;
|
||||
case SyntaxKind.GreaterThanToken: return ExprKind.GT;
|
||||
case SyntaxKind.AsteriskToken: return ExprKind.MUL;
|
||||
case SyntaxKind.AmpersandAmpersandToken: return ExprKind.LOG_AND;
|
||||
case SyntaxKind.EqualsEqualsToken: return ExprKind.EQ;
|
||||
case SyntaxKind.PercentToken: return ExprKind.REM;
|
||||
case SyntaxKind.MinusToken: return ExprKind.SUB;
|
||||
case SyntaxKind.AmpersandToken: return ExprKind.BIT_AND;
|
||||
case SyntaxKind.BarToken: return ExprKind.BIT_OR;
|
||||
case SyntaxKind.SlashToken: return ExprKind.DIV;
|
||||
case SyntaxKind.ExclamationEqualsToken: return ExprKind.NE;
|
||||
case SyntaxKind.AsKeyword: return ExprKind.AS;
|
||||
case SyntaxKind.IsKeyword: return ExprKind.IS;
|
||||
case SyntaxKind.BarBarToken: return ExprKind.LOG_OR;
|
||||
case SyntaxKind.GreaterThanEqualsToken: return ExprKind.GE;
|
||||
case SyntaxKind.GreaterThanGreaterThanToken: return ExprKind.RSHIFT;
|
||||
case SyntaxKind.LessThanLessThanToken: return ExprKind.LSHIFT;
|
||||
case SyntaxKind.CaretToken: return ExprKind.BIT_XOR;
|
||||
case SyntaxKind.QuestionQuestionToken: return ExprKind.NULL_COALESCING;
|
||||
// !! And the rest
|
||||
default:
|
||||
cx.ModelError("Unhandled operator type {0}", kind);
|
||||
return ExprKind.UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.Kinds;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
class Cast : Expression<CastExpressionSyntax>
|
||||
{
|
||||
Cast(ExpressionNodeInfo info) : base(info.SetKind(UnaryOperatorKind(info.Context, ExprKind.CAST, info.Node))) { }
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new Cast(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
Create(cx, Syntax.Expression, this, 0);
|
||||
|
||||
if (Kind == ExprKind.CAST)
|
||||
// Type cast
|
||||
TypeAccess.Create(new ExpressionNodeInfo(cx, Syntax.Type, this, 1));
|
||||
else
|
||||
{
|
||||
// Type conversion
|
||||
OperatorCall(Syntax);
|
||||
TypeMention.Create(cx, Syntax.Type, this, Type);
|
||||
}
|
||||
}
|
||||
|
||||
public override Microsoft.CodeAnalysis.Location ReportingLocation => Syntax.GetLocation();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.Kinds;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
class Checked : Expression<CheckedExpressionSyntax>
|
||||
{
|
||||
Checked(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.CHECKED)) { }
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new Checked(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
Create(cx, Syntax.Expression, this, 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.Kinds;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
class Conditional : Expression<ConditionalExpressionSyntax>
|
||||
{
|
||||
Conditional(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.CONDITIONAL)) { }
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new Conditional(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
Create(cx, Syntax.Condition, this, 0);
|
||||
Create(cx, Syntax.WhenTrue, this, 1);
|
||||
Create(cx, Syntax.WhenFalse, this, 2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.Kinds;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
class Default : Expression<DefaultExpressionSyntax>
|
||||
{
|
||||
Default(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.DEFAULT)) { }
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new Default(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
TypeAccess.Create(cx, Syntax.Type, this, 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.Kinds;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
class Discard : Expression<NameSyntax>
|
||||
{
|
||||
public Discard(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.DISCARD))
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.CSharp.Populators;
|
||||
using Semmle.Extraction.Kinds;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Semmle.Extraction.Entities;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
abstract class ElementAccess : Expression<ExpressionSyntax>
|
||||
{
|
||||
protected ElementAccess(ExpressionNodeInfo info, ExpressionSyntax qualifier, BracketedArgumentListSyntax argumentList)
|
||||
: base(info.SetKind(GetKind(info.Context, qualifier)))
|
||||
{
|
||||
Qualifier = qualifier;
|
||||
ArgumentList = argumentList;
|
||||
}
|
||||
|
||||
readonly ExpressionSyntax Qualifier;
|
||||
readonly BracketedArgumentListSyntax ArgumentList;
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
if (Kind == ExprKind.POINTER_INDIRECTION)
|
||||
{
|
||||
var qualifierInfo = new ExpressionNodeInfo(cx, Qualifier, this, 0);
|
||||
var add = new Expression(new ExpressionInfo(cx, qualifierInfo.Type, Location, ExprKind.ADD, this, 0, false, null));
|
||||
qualifierInfo.SetParent(add, 0);
|
||||
CreateFromNode(qualifierInfo);
|
||||
PopulateArguments(cx, ArgumentList, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
var child = -1;
|
||||
Create(cx, Qualifier, this, child++);
|
||||
foreach (var a in ArgumentList.Arguments)
|
||||
{
|
||||
cx.Extract(a, this, child++);
|
||||
}
|
||||
|
||||
var symbolInfo = cx.GetSymbolInfo(base.Syntax);
|
||||
|
||||
var indexer = symbolInfo.Symbol as IPropertySymbol;
|
||||
if (indexer != null)
|
||||
{
|
||||
cx.Emit(Tuples.expr_access(this, Indexer.Create(cx, indexer)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed override Microsoft.CodeAnalysis.Location ReportingLocation => base.ReportingLocation;
|
||||
|
||||
static ExprKind GetKind(Context cx, ExpressionSyntax qualifier)
|
||||
{
|
||||
var qualifierType = cx.GetType(qualifier);
|
||||
|
||||
// This is a compilation error, so make a guess and continue.
|
||||
if (qualifierType == null) return ExprKind.ARRAY_ACCESS;
|
||||
|
||||
if (qualifierType.TypeKind == Microsoft.CodeAnalysis.TypeKind.Pointer)
|
||||
{
|
||||
// Convert expressions of the form a[b] into *(a+b)
|
||||
return ExprKind.POINTER_INDIRECTION;
|
||||
}
|
||||
|
||||
return IsDynamic(cx, qualifier) ?
|
||||
ExprKind.DYNAMIC_ELEMENT_ACCESS :
|
||||
qualifierType.TypeKind == Microsoft.CodeAnalysis.TypeKind.Array ?
|
||||
ExprKind.ARRAY_ACCESS :
|
||||
ExprKind.INDEXER_ACCESS;
|
||||
}
|
||||
}
|
||||
|
||||
class NormalElementAccess : ElementAccess
|
||||
{
|
||||
NormalElementAccess(ExpressionNodeInfo info)
|
||||
: base(info, ((ElementAccessExpressionSyntax)info.Node).Expression, ((ElementAccessExpressionSyntax)info.Node).ArgumentList) { }
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new NormalElementAccess(info).TryPopulate();
|
||||
}
|
||||
|
||||
class BindingElementAccess : ElementAccess
|
||||
{
|
||||
BindingElementAccess(ExpressionNodeInfo info)
|
||||
: base(info, FindConditionalQualifier(info.Node), ((ElementBindingExpressionSyntax)info.Node).ArgumentList) { }
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new BindingElementAccess(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
base.Populate();
|
||||
MakeConditional();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.CSharp.Populators;
|
||||
using Semmle.Extraction.Kinds;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
internal static class Factory
|
||||
{
|
||||
internal static Expression Create(ExpressionNodeInfo info)
|
||||
{
|
||||
// Some expressions can be extremely deep (e.g. string + string + string ...)
|
||||
// to the extent that the stack has been known to overflow.
|
||||
using (info.Context.StackGuard)
|
||||
{
|
||||
if (info.Node == null)
|
||||
{
|
||||
info.Context.ModelError("Attempt to create a null expression");
|
||||
return new Unknown(info);
|
||||
}
|
||||
|
||||
switch (info.Node.Kind())
|
||||
{
|
||||
case SyntaxKind.AddExpression:
|
||||
case SyntaxKind.SubtractExpression:
|
||||
case SyntaxKind.LessThanExpression:
|
||||
case SyntaxKind.LessThanOrEqualExpression:
|
||||
case SyntaxKind.GreaterThanExpression:
|
||||
case SyntaxKind.GreaterThanOrEqualExpression:
|
||||
case SyntaxKind.MultiplyExpression:
|
||||
case SyntaxKind.LogicalAndExpression:
|
||||
case SyntaxKind.EqualsExpression:
|
||||
case SyntaxKind.ModuloExpression:
|
||||
case SyntaxKind.BitwiseAndExpression:
|
||||
case SyntaxKind.BitwiseOrExpression:
|
||||
case SyntaxKind.DivideExpression:
|
||||
case SyntaxKind.NotEqualsExpression:
|
||||
case SyntaxKind.LogicalOrExpression:
|
||||
case SyntaxKind.IsExpression:
|
||||
case SyntaxKind.AsExpression:
|
||||
case SyntaxKind.RightShiftExpression:
|
||||
case SyntaxKind.LeftShiftExpression:
|
||||
case SyntaxKind.ExclusiveOrExpression:
|
||||
case SyntaxKind.CoalesceExpression:
|
||||
return Binary.Create(info);
|
||||
|
||||
case SyntaxKind.FalseLiteralExpression:
|
||||
case SyntaxKind.TrueLiteralExpression:
|
||||
case SyntaxKind.StringLiteralExpression:
|
||||
case SyntaxKind.NullLiteralExpression:
|
||||
case SyntaxKind.NumericLiteralExpression:
|
||||
case SyntaxKind.CharacterLiteralExpression:
|
||||
case SyntaxKind.DefaultLiteralExpression:
|
||||
return Literal.Create(info);
|
||||
|
||||
case SyntaxKind.InvocationExpression:
|
||||
return Invocation.Create(info);
|
||||
|
||||
case SyntaxKind.PostIncrementExpression:
|
||||
return PostfixUnary.Create(info.SetKind(ExprKind.POST_INCR), ((PostfixUnaryExpressionSyntax)info.Node).Operand);
|
||||
|
||||
case SyntaxKind.PostDecrementExpression:
|
||||
return PostfixUnary.Create(info.SetKind(ExprKind.POST_DECR), ((PostfixUnaryExpressionSyntax)info.Node).Operand);
|
||||
|
||||
case SyntaxKind.AwaitExpression:
|
||||
return Await.Create(info);
|
||||
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
return NormalElementAccess.Create(info);
|
||||
|
||||
case SyntaxKind.SimpleAssignmentExpression:
|
||||
case SyntaxKind.OrAssignmentExpression:
|
||||
case SyntaxKind.AndAssignmentExpression:
|
||||
case SyntaxKind.SubtractAssignmentExpression:
|
||||
case SyntaxKind.AddAssignmentExpression:
|
||||
case SyntaxKind.MultiplyAssignmentExpression:
|
||||
case SyntaxKind.ExclusiveOrAssignmentExpression:
|
||||
case SyntaxKind.LeftShiftAssignmentExpression:
|
||||
case SyntaxKind.RightShiftAssignmentExpression:
|
||||
case SyntaxKind.DivideAssignmentExpression:
|
||||
case SyntaxKind.ModuloAssignmentExpression:
|
||||
return Assignment.Create(info);
|
||||
|
||||
case SyntaxKind.ObjectCreationExpression:
|
||||
return ExplicitObjectCreation.Create(info);
|
||||
|
||||
case SyntaxKind.ArrayCreationExpression:
|
||||
return ExplicitArrayCreation.Create(info);
|
||||
|
||||
case SyntaxKind.ObjectInitializerExpression:
|
||||
return ObjectInitializer.Create(info);
|
||||
|
||||
case SyntaxKind.ArrayInitializerExpression:
|
||||
return ImplicitArrayInitializer.Create(info);
|
||||
|
||||
case SyntaxKind.CollectionInitializerExpression:
|
||||
return CollectionInitializer.Create(info);
|
||||
|
||||
case SyntaxKind.ConditionalAccessExpression:
|
||||
return MemberAccess.Create(info, (ConditionalAccessExpressionSyntax)info.Node);
|
||||
|
||||
case SyntaxKind.SimpleMemberAccessExpression:
|
||||
return MemberAccess.Create(info, (MemberAccessExpressionSyntax)info.Node);
|
||||
|
||||
case SyntaxKind.UnaryMinusExpression:
|
||||
return Unary.Create(info.SetKind(ExprKind.MINUS));
|
||||
|
||||
case SyntaxKind.UnaryPlusExpression:
|
||||
return Unary.Create(info.SetKind(ExprKind.PLUS));
|
||||
|
||||
case SyntaxKind.SimpleLambdaExpression:
|
||||
return Lambda.Create(info, (SimpleLambdaExpressionSyntax)info.Node);
|
||||
|
||||
case SyntaxKind.ParenthesizedLambdaExpression:
|
||||
return Lambda.Create(info, (ParenthesizedLambdaExpressionSyntax)info.Node);
|
||||
|
||||
case SyntaxKind.ConditionalExpression:
|
||||
return Conditional.Create(info);
|
||||
|
||||
case SyntaxKind.CastExpression:
|
||||
return Cast.Create(info);
|
||||
|
||||
case SyntaxKind.ParenthesizedExpression:
|
||||
return Create(info.SetNode(((ParenthesizedExpressionSyntax)info.Node).Expression));
|
||||
|
||||
case SyntaxKind.PointerType:
|
||||
case SyntaxKind.ArrayType:
|
||||
case SyntaxKind.PredefinedType:
|
||||
case SyntaxKind.NullableType:
|
||||
return TypeAccess.Create(info);
|
||||
|
||||
case SyntaxKind.TypeOfExpression:
|
||||
return TypeOf.Create(info);
|
||||
|
||||
case SyntaxKind.QualifiedName:
|
||||
case SyntaxKind.IdentifierName:
|
||||
case SyntaxKind.AliasQualifiedName:
|
||||
case SyntaxKind.GenericName:
|
||||
return Name.Create(info);
|
||||
|
||||
case SyntaxKind.LogicalNotExpression:
|
||||
return Unary.Create(info.SetKind(ExprKind.LOG_NOT));
|
||||
|
||||
case SyntaxKind.BitwiseNotExpression:
|
||||
return Unary.Create(info.SetKind(ExprKind.BIT_NOT));
|
||||
|
||||
case SyntaxKind.PreIncrementExpression:
|
||||
return Unary.Create(info.SetKind(ExprKind.PRE_INCR));
|
||||
|
||||
case SyntaxKind.PreDecrementExpression:
|
||||
return Unary.Create(info.SetKind(ExprKind.PRE_DECR));
|
||||
|
||||
case SyntaxKind.ThisExpression:
|
||||
return This.CreateExplicit(info);
|
||||
|
||||
case SyntaxKind.AddressOfExpression:
|
||||
return Unary.Create(info.SetKind(ExprKind.ADDRESS_OF));
|
||||
|
||||
case SyntaxKind.PointerIndirectionExpression:
|
||||
return Unary.Create(info.SetKind(ExprKind.POINTER_INDIRECTION));
|
||||
|
||||
case SyntaxKind.DefaultExpression:
|
||||
return Default.Create(info);
|
||||
|
||||
case SyntaxKind.CheckedExpression:
|
||||
return Checked.Create(info);
|
||||
|
||||
case SyntaxKind.UncheckedExpression:
|
||||
return Unchecked.Create(info);
|
||||
|
||||
case SyntaxKind.BaseExpression:
|
||||
return Base.Create(info);
|
||||
|
||||
case SyntaxKind.AnonymousMethodExpression:
|
||||
return Lambda.Create(info, (AnonymousMethodExpressionSyntax)info.Node);
|
||||
|
||||
case SyntaxKind.ImplicitArrayCreationExpression:
|
||||
return ImplicitArrayCreation.Create(info);
|
||||
|
||||
case SyntaxKind.AnonymousObjectCreationExpression:
|
||||
return ImplicitObjectCreation.Create(info);
|
||||
|
||||
case SyntaxKind.ComplexElementInitializerExpression:
|
||||
return CollectionInitializer.Create(info);
|
||||
|
||||
case SyntaxKind.SizeOfExpression:
|
||||
return SizeOf.Create(info);
|
||||
|
||||
case SyntaxKind.PointerMemberAccessExpression:
|
||||
return PointerMemberAccess.Create(info);
|
||||
|
||||
case SyntaxKind.QueryExpression:
|
||||
return Query.Create(info);
|
||||
|
||||
case SyntaxKind.InterpolatedStringExpression:
|
||||
return InterpolatedString.Create(info);
|
||||
|
||||
case SyntaxKind.MemberBindingExpression:
|
||||
return MemberAccess.Create(info, (MemberBindingExpressionSyntax)info.Node);
|
||||
|
||||
case SyntaxKind.ElementBindingExpression:
|
||||
return BindingElementAccess.Create(info);
|
||||
|
||||
case SyntaxKind.StackAllocArrayCreationExpression:
|
||||
return StackAllocArrayCreation.Create(info);
|
||||
|
||||
case SyntaxKind.ArgListExpression:
|
||||
return ArgList.Create(info);
|
||||
|
||||
case SyntaxKind.RefTypeExpression:
|
||||
return RefType.Create(info);
|
||||
|
||||
case SyntaxKind.RefValueExpression:
|
||||
return RefValue.Create(info);
|
||||
|
||||
case SyntaxKind.MakeRefExpression:
|
||||
return MakeRef.Create(info);
|
||||
|
||||
case SyntaxKind.ThrowExpression:
|
||||
return Throw.Create(info);
|
||||
|
||||
case SyntaxKind.DeclarationExpression:
|
||||
return VariableDeclaration.Create(info.Context, (DeclarationExpressionSyntax)info.Node, info.Parent, info.Child);
|
||||
|
||||
case SyntaxKind.TupleExpression:
|
||||
return Tuple.Create(info);
|
||||
|
||||
case SyntaxKind.RefExpression:
|
||||
return Ref.Create(info);
|
||||
|
||||
case SyntaxKind.IsPatternExpression:
|
||||
return IsPattern.Create(info);
|
||||
|
||||
default:
|
||||
info.Context.ModelError(info.Node, "Unhandled expression '{0}' of kind '{1}'", info.Node, info.Node.Kind());
|
||||
return new Unknown(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.CSharp.Populators;
|
||||
using Semmle.Extraction.Kinds;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
class ImplicitCast : Expression
|
||||
{
|
||||
public Expression Expr
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public ImplicitCast(ExpressionNodeInfo info)
|
||||
: base(new ExpressionInfo(info.Context, Type.Create(info.Context, info.ConvertedType), info.Location, ExprKind.CAST, info.Parent, info.Child, true, info.ExprValue))
|
||||
{
|
||||
Expr = Factory.Create(new ExpressionNodeInfo(cx, info.Node, this, 0));
|
||||
}
|
||||
|
||||
public ImplicitCast(ExpressionNodeInfo info, IMethodSymbol method)
|
||||
: base(new ExpressionInfo(info.Context, Type.Create(info.Context, info.ConvertedType), info.Location, ExprKind.OPERATOR_INVOCATION, info.Parent, info.Child, true, info.ExprValue) )
|
||||
{
|
||||
Expr = Factory.Create(info.SetParent(this, 0));
|
||||
|
||||
var target = Method.Create(cx, method);
|
||||
if (target != null)
|
||||
cx.Emit(Tuples.expr_call(this, target));
|
||||
else
|
||||
cx.ModelError(info.Node, "Failed to resolve target for operator invocation");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new expression, adding casts as required.
|
||||
/// </summary>
|
||||
/// <param name="cx">The extraction context.</param>
|
||||
/// <param name="node">The expression node.</param>
|
||||
/// <param name="parent">The parent of the expression.</param>
|
||||
/// <param name="child">The child number.</param>
|
||||
/// <param name="type">A type hint.</param>
|
||||
/// <returns>A new expression.</returns>
|
||||
public static Expression Create(ExpressionNodeInfo info)
|
||||
{
|
||||
var resolvedType = info.ResolvedType;
|
||||
var convertedType = info.ConvertedType;
|
||||
var conversion = info.Conversion;
|
||||
|
||||
if (conversion.MethodSymbol != null)
|
||||
{
|
||||
bool convertedToDelegate = Type.IsDelegate(convertedType);
|
||||
|
||||
if (convertedToDelegate)
|
||||
{
|
||||
var objectCreation = info.Parent as ExplicitObjectCreation;
|
||||
bool isExplicitConversion = objectCreation != null && objectCreation.Kind == ExprKind.EXPLICIT_DELEGATE_CREATION;
|
||||
|
||||
if (!isExplicitConversion)
|
||||
{
|
||||
info.Kind = ExprKind.IMPLICIT_DELEGATE_CREATION;
|
||||
var parent = new Expression(info);
|
||||
return Factory.Create(new ExpressionNodeInfo(info.Context, info.Node, parent, 0));
|
||||
}
|
||||
|
||||
info.Kind = ExprKind.UNKNOWN;
|
||||
return Factory.Create(info);
|
||||
}
|
||||
|
||||
if (resolvedType != null)
|
||||
return new ImplicitCast(info, conversion.MethodSymbol);
|
||||
}
|
||||
|
||||
bool implicitUpcast = conversion.IsImplicit &&
|
||||
convertedType != null &&
|
||||
!conversion.IsBoxing &&
|
||||
(
|
||||
resolvedType == null ||
|
||||
conversion.IsReference ||
|
||||
convertedType.SpecialType == SpecialType.System_Object)
|
||||
;
|
||||
|
||||
if (!conversion.IsIdentity && !implicitUpcast)
|
||||
{
|
||||
return new ImplicitCast(info);
|
||||
}
|
||||
|
||||
// Default: Just create the expression without a conversion.
|
||||
return Factory.Create(info);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.CSharp.Populators;
|
||||
using Semmle.Extraction.Entities;
|
||||
using Semmle.Extraction.Kinds;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
abstract class Initializer : Expression<InitializerExpressionSyntax>
|
||||
{
|
||||
protected Initializer(ExpressionNodeInfo info) : base(info) { }
|
||||
}
|
||||
|
||||
class ArrayInitializer : Expression<InitializerExpressionSyntax>
|
||||
{
|
||||
ArrayInitializer(ExpressionNodeInfo info) : base(info.SetType(Type.Create(info.Context, null)).SetKind(ExprKind.ARRAY_INIT)) { }
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new ArrayInitializer(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
var child = 0;
|
||||
foreach (var e in Syntax.Expressions)
|
||||
{
|
||||
if (e.Kind() == SyntaxKind.ArrayInitializerExpression)
|
||||
{
|
||||
// Recursively create another array initializer
|
||||
Create(new ExpressionNodeInfo(cx, (InitializerExpressionSyntax)e, this, child++));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create the expression normally.
|
||||
Create(cx, e, this, child++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Array initializer { ..., ... }.
|
||||
class ImplicitArrayInitializer : Initializer
|
||||
{
|
||||
ImplicitArrayInitializer(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.ARRAY_CREATION)) { }
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new ImplicitArrayInitializer(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
ArrayInitializer.Create(new ExpressionNodeInfo(cx, Syntax, this, -1));
|
||||
cx.Emit(Tuples.implicitly_typed_array_creation(this));
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectInitializer : Initializer
|
||||
{
|
||||
ObjectInitializer(ExpressionNodeInfo info)
|
||||
: base(info.SetKind(ExprKind.OBJECT_INIT)) { }
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new ObjectInitializer(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
var child = 0;
|
||||
|
||||
foreach (var init in Syntax.Expressions)
|
||||
{
|
||||
var assignment = init as AssignmentExpressionSyntax;
|
||||
|
||||
if (assignment != null)
|
||||
{
|
||||
var assignmentEntity = new Expression(new ExpressionNodeInfo(cx, init, this, child++).SetKind(ExprKind.SIMPLE_ASSIGN));
|
||||
|
||||
CreateFromNode(new ExpressionNodeInfo(cx, assignment.Right, assignmentEntity, 0));
|
||||
|
||||
var target = cx.GetSymbolInfo(assignment.Left);
|
||||
if (target.Symbol == null)
|
||||
{
|
||||
cx.ModelError(assignment, "Unknown object initializer");
|
||||
new Unknown(new ExpressionNodeInfo(cx, assignment.Left, assignmentEntity, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
Access.Create(new ExpressionNodeInfo(cx, assignment.Left, assignmentEntity, 1), target.Symbol, false, cx.CreateEntity(target.Symbol));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cx.ModelError(init, "Unexpected object initialization");
|
||||
Create(cx, init, this, child++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CollectionInitializer : Initializer
|
||||
{
|
||||
CollectionInitializer(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.COLLECTION_INIT)) { }
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new CollectionInitializer(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
var child = 0;
|
||||
foreach (var i in Syntax.Expressions)
|
||||
{
|
||||
var collectionInfo = cx.Model(Syntax).GetCollectionInitializerSymbolInfo(i);
|
||||
var addMethod = Method.Create(cx, collectionInfo.Symbol as IMethodSymbol);
|
||||
var voidType = Type.Create(cx, cx.Compilation.GetSpecialType(SpecialType.System_Void));
|
||||
|
||||
var invocation = new Expression(new ExpressionInfo(cx, voidType, cx.Create(i.GetLocation()), ExprKind.METHOD_INVOCATION, this, child++, false, null));
|
||||
|
||||
if (addMethod != null)
|
||||
cx.Emit(Tuples.expr_call(invocation, addMethod));
|
||||
else
|
||||
cx.ModelError(Syntax, "Unable to find an Add() method for collection initializer");
|
||||
|
||||
if (i.Kind() == SyntaxKind.ComplexElementInitializerExpression)
|
||||
{
|
||||
// Arrays of the form new Foo { { 1,2 }, { 3, 4 } }
|
||||
// where the arguments { 1, 2 } are passed to the Add() method.
|
||||
|
||||
var init = (InitializerExpressionSyntax)i;
|
||||
|
||||
int addChild = 0;
|
||||
foreach (var arg in init.Expressions)
|
||||
{
|
||||
Create(cx, arg, invocation, addChild++);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Create(cx, i, invocation, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Semmle.Extraction.Kinds;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Semmle.Extraction.Entities;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
class InterpolatedString : Expression<InterpolatedStringExpressionSyntax>
|
||||
{
|
||||
InterpolatedString(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.INTERPOLATED_STRING)) { }
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new InterpolatedString(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
var child = 0;
|
||||
foreach (var c in Syntax.Contents)
|
||||
{
|
||||
switch (c.Kind())
|
||||
{
|
||||
case SyntaxKind.Interpolation:
|
||||
var interpolation = (InterpolationSyntax)c;
|
||||
Create(cx, interpolation.Expression, this, child++);
|
||||
break;
|
||||
case SyntaxKind.InterpolatedStringText:
|
||||
// Create a string literal
|
||||
var interpolatedText = (InterpolatedStringTextSyntax)c;
|
||||
new Expression(new ExpressionInfo(cx, Type, cx.Create(c.GetLocation()), ExprKind.STRING_LITERAL, this, child++, false, interpolatedText.TextToken.Text));
|
||||
break;
|
||||
default:
|
||||
throw new InternalError(c, "Unhandled interpolation kind {0}", c.Kind());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Semmle.Extraction.Kinds;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
class Invocation : Expression<InvocationExpressionSyntax>
|
||||
{
|
||||
Invocation(ExpressionNodeInfo info)
|
||||
: base(info.SetKind(GetKind(info)))
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
readonly ExpressionNodeInfo info;
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new Invocation(info).TryPopulate();
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
if (IsNameof(Syntax))
|
||||
{
|
||||
PopulateArguments(cx, Syntax.ArgumentList, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
var child = -1;
|
||||
string memberName = null;
|
||||
var target = TargetSymbol;
|
||||
switch (Syntax.Expression)
|
||||
{
|
||||
case MemberAccessExpressionSyntax memberAccess:
|
||||
memberName = memberAccess.Name.Identifier.Text;
|
||||
if (Syntax.Expression.Kind() == SyntaxKind.SimpleMemberAccessExpression)
|
||||
// Qualified method call; `x.M()`
|
||||
Create(cx, memberAccess.Expression, this, child++);
|
||||
else
|
||||
// Pointer member access; `x->M()`
|
||||
Create(cx, Syntax.Expression, this, child++);
|
||||
break;
|
||||
case MemberBindingExpressionSyntax memberBinding:
|
||||
// Conditionally qualified method call; `x?.M()`
|
||||
memberName = memberBinding.Name.Identifier.Text;
|
||||
Create(cx, FindConditionalQualifier(memberBinding), this, child++);
|
||||
MakeConditional();
|
||||
break;
|
||||
case SimpleNameSyntax simpleName when (Kind == ExprKind.METHOD_INVOCATION):
|
||||
// Unqualified method call; `M()`
|
||||
memberName = simpleName.Identifier.Text;
|
||||
if (target != null && !target.IsStatic)
|
||||
{
|
||||
// Implicit `this` qualifier; add explicitly
|
||||
var callingMethod = cx.Model(Syntax).GetEnclosingSymbol(Location.symbol.SourceSpan.Start) as IMethodSymbol;
|
||||
|
||||
if (callingMethod == null)
|
||||
cx.ModelError(Syntax, "Couldn't determine implicit this type");
|
||||
else
|
||||
This.CreateImplicit(cx, Type.Create(cx, callingMethod.ContainingType), Location, this, child++);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No implicit `this` qualifier
|
||||
child++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Delegate call; `d()`
|
||||
Create(cx, Syntax.Expression, this, child++);
|
||||
break;
|
||||
}
|
||||
|
||||
var isDynamicCall = IsDynamicCall(info);
|
||||
if (isDynamicCall)
|
||||
{
|
||||
if (memberName != null)
|
||||
cx.Emit(Tuples.dynamic_member_name(this, memberName));
|
||||
else
|
||||
cx.ModelError(Syntax, "Unable to get name for dynamic call.");
|
||||
}
|
||||
|
||||
PopulateArguments(cx, Syntax.ArgumentList, child);
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
if (!isDynamicCall && !IsDelegateCall(info))
|
||||
cx.ModelError(Syntax, "Unable to resolve target for call. (Compilation error?)");
|
||||
return;
|
||||
}
|
||||
|
||||
var targetKey = Method.Create(cx, target);
|
||||
cx.Emit(Tuples.expr_call(this, targetKey));
|
||||
}
|
||||
|
||||
static bool IsDynamicCall(ExpressionNodeInfo info)
|
||||
{
|
||||
// Either the qualifier (Expression) is dynamic,
|
||||
// or one of the arguments is dynamic.
|
||||
var node = (InvocationExpressionSyntax)info.Node;
|
||||
return !IsDelegateCall(info) &&
|
||||
(IsDynamic(info.Context, node.Expression) || node.ArgumentList.Arguments.Any(arg => IsDynamic(info.Context, arg.Expression)));
|
||||
}
|
||||
|
||||
public SymbolInfo SymbolInfo => info.SymbolInfo;
|
||||
|
||||
public IMethodSymbol TargetSymbol
|
||||
{
|
||||
get
|
||||
{
|
||||
var si = SymbolInfo;
|
||||
|
||||
if (si.Symbol != null) return si.Symbol as IMethodSymbol;
|
||||
|
||||
if (si.CandidateReason == CandidateReason.OverloadResolutionFailure)
|
||||
{
|
||||
// This seems to be a bug in Roslyn
|
||||
// For some reason, typeof(X).InvokeMember(...) fails to resolve the correct
|
||||
// InvokeMember() method, even though the number of parameters clearly identifies the correct method
|
||||
|
||||
var candidates = si.CandidateSymbols.
|
||||
OfType<IMethodSymbol>().
|
||||
Where(method => method.Parameters.Length >= Syntax.ArgumentList.Arguments.Count).
|
||||
Where(method => method.Parameters.Count(p => !p.HasExplicitDefaultValue) <= Syntax.ArgumentList.Arguments.Count);
|
||||
|
||||
return cx.Extractor.Standalone ?
|
||||
candidates.FirstOrDefault() :
|
||||
candidates.SingleOrDefault();
|
||||
}
|
||||
|
||||
return si.Symbol as IMethodSymbol;
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsDelegateCall(ExpressionNodeInfo info)
|
||||
{
|
||||
var si = info.SymbolInfo;
|
||||
|
||||
if (si.CandidateReason == CandidateReason.OverloadResolutionFailure &&
|
||||
si.CandidateSymbols.OfType<IMethodSymbol>().All(s => s.MethodKind == MethodKind.DelegateInvoke))
|
||||
return true;
|
||||
|
||||
// Delegate variable is a dynamic
|
||||
var node = (InvocationExpressionSyntax)info.Node;
|
||||
if (si.CandidateReason == CandidateReason.LateBound &&
|
||||
node.Expression is IdentifierNameSyntax &&
|
||||
IsDynamic(info.Context, node.Expression) &&
|
||||
si.Symbol == null)
|
||||
return true;
|
||||
|
||||
return si.Symbol != null &&
|
||||
si.Symbol.Kind == SymbolKind.Method &&
|
||||
((IMethodSymbol)si.Symbol).MethodKind == MethodKind.DelegateInvoke;
|
||||
}
|
||||
|
||||
static bool IsLocalFunctionInvocation(ExpressionNodeInfo info)
|
||||
{
|
||||
var target = info.SymbolInfo.Symbol as IMethodSymbol;
|
||||
return target != null && target.MethodKind == MethodKind.LocalFunction;
|
||||
}
|
||||
|
||||
static ExprKind GetKind(ExpressionNodeInfo info)
|
||||
{
|
||||
return IsNameof((InvocationExpressionSyntax)info.Node) ?
|
||||
ExprKind.NAMEOF :
|
||||
IsDelegateCall(info) ?
|
||||
ExprKind.DELEGATE_INVOCATION :
|
||||
IsLocalFunctionInvocation(info) ?
|
||||
ExprKind.LOCAL_FUNCTION_INVOCATION :
|
||||
ExprKind.METHOD_INVOCATION;
|
||||
}
|
||||
|
||||
static bool IsNameof(InvocationExpressionSyntax syntax)
|
||||
{
|
||||
// Odd that this is not a separate expression type.
|
||||
// Maybe it will be in the future.
|
||||
var id = syntax.Expression as IdentifierNameSyntax;
|
||||
return id != null && id.Identifier.Text == "nameof";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Semmle.Extraction.Kinds;
|
||||
using Semmle.Extraction.Entities;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
{
|
||||
class IsPattern : Expression<IsPatternExpressionSyntax>
|
||||
{
|
||||
IsPattern(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.IS))
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Populate()
|
||||
{
|
||||
var constantPattern = Syntax.Pattern as ConstantPatternSyntax;
|
||||
if (constantPattern != null)
|
||||
{
|
||||
Create(cx, Syntax.Expression, this, 0);
|
||||
Create(cx, constantPattern.Expression, this, 3);
|
||||
return;
|
||||
}
|
||||
|
||||
var pattern = Syntax.Pattern as DeclarationPatternSyntax;
|
||||
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new InternalError(Syntax, "Is-pattern not handled");
|
||||
}
|
||||
|
||||
Create(cx, Syntax.Expression, this, 0);
|
||||
TypeAccess.Create(cx, pattern.Type, this, 1);
|
||||
|
||||
var symbol = cx.Model(Syntax).GetDeclaredSymbol(pattern.Designation) as ILocalSymbol;
|
||||
if (symbol != null)
|
||||
{
|
||||
var type = Type.Create(cx, symbol.Type);
|
||||
var isVar = pattern.Type.IsVar;
|
||||
VariableDeclaration.Create(cx, symbol, type, cx.Create(pattern.GetLocation()), cx.Create(pattern.Designation.GetLocation()), isVar, this, 2);
|
||||
}
|
||||
}
|
||||
|
||||
public static Expression Create(ExpressionNodeInfo info) => new IsPattern(info).TryPopulate();
|
||||
}
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче