diff --git a/csharp/extractor/.gitignore b/csharp/extractor/.gitignore
new file mode 100644
index 000000000000..f81ecc73dffa
--- /dev/null
+++ b/csharp/extractor/.gitignore
@@ -0,0 +1,13 @@
+obj/
+TestResults/
+*.manifest
+*.pdb
+*.suo
+*.mdb
+*.vsmdi
+csharp.log
+**/bin/Debug
+**/bin/Release
+*.tlog
+.vs
+*.user
\ No newline at end of file
diff --git a/csharp/extractor/CSharpExtractor.sln b/csharp/extractor/CSharpExtractor.sln
new file mode 100644
index 000000000000..9f0d615ed387
--- /dev/null
+++ b/csharp/extractor/CSharpExtractor.sln
@@ -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
diff --git a/csharp/extractor/Semmle.Autobuild.Tests/BuildScripts.cs b/csharp/extractor/Semmle.Autobuild.Tests/BuildScripts.cs
new file mode 100644
index 000000000000..d49493c55480
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild.Tests/BuildScripts.cs
@@ -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
+{
+ ///
+ /// 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.
+ ///
+ class TestActions : IBuildActions
+ {
+ ///
+ /// List of strings passed to FileDelete.
+ ///
+ public IList FileDeleteIn = new List();
+
+ void IBuildActions.FileDelete(string file)
+ {
+ FileDeleteIn.Add(file);
+ }
+
+ public IList FileExistsIn = new List();
+ public IDictionary FileExists = new Dictionary();
+
+ 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 RunProcessIn = new List();
+ public IDictionary RunProcess = new Dictionary();
+ public IDictionary RunProcessOut = new Dictionary();
+ public IDictionary RunProcessWorkingDirectory = new Dictionary();
+
+ int IBuildActions.RunProcess(string cmd, string args, string workingDirectory, IDictionary env, out IList 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 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 DirectoryDeleteIn = new List();
+
+ void IBuildActions.DirectoryDelete(string dir, bool recursive)
+ {
+ DirectoryDeleteIn.Add(dir);
+ }
+
+ public IDictionary DirectoryExists = new Dictionary();
+ public IList DirectoryExistsIn = new List();
+
+ 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 GetEnvironmentVariable = new Dictionary();
+
+ 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 EnumerateFiles = new Dictionary();
+
+ IEnumerable IBuildActions.EnumerateFiles(string dir)
+ {
+ if (EnumerateFiles.TryGetValue(dir, out var str))
+ return str.Split("\n");
+ throw new ArgumentException("Missing EnumerateFiles " + dir);
+ }
+
+ public IDictionary EnumerateDirectories = new Dictionary();
+
+ IEnumerable IBuildActions.EnumerateDirectories(string dir)
+ {
+ if (EnumerateDirectories.TryGetValue(dir, out var str))
+ return string.IsNullOrEmpty(str) ? Enumerable.Empty() : 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)
+ {
+ }
+ }
+
+ ///
+ /// A fake solution to build.
+ ///
+ class TestSolution : ISolution
+ {
+ public IEnumerable Projects => throw new NotImplementedException();
+
+ public IEnumerable 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 StartCallbackIn = new List();
+
+ void StartCallback(string s)
+ {
+ StartCallbackIn.Add(s);
+ }
+
+ // Records the arguments passed to EndCallback
+ IList EndCallbackIn = new List();
+ IList EndCallbackReturn = new List();
+
+ 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
+
+
+ Exe
+ netcoreapp2.0
+ false
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/csharp/extractor/Semmle.Autobuild/AspBuildRule.cs b/csharp/extractor/Semmle.Autobuild/AspBuildRule.cs
new file mode 100644
index 000000000000..7765e94f5e16
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/AspBuildRule.cs
@@ -0,0 +1,20 @@
+using System.IO;
+
+namespace Semmle.Autobuild
+{
+ ///
+ /// ASP extraction.
+ ///
+ 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;
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/AutobuildOptions.cs b/csharp/extractor/Semmle.Autobuild/AutobuildOptions.cs
new file mode 100644
index 000000000000..53ecf697721e
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/AutobuildOptions.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Linq;
+
+namespace Semmle.Autobuild
+{
+ ///
+ /// Encapsulates build options.
+ ///
+ 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;
+
+ ///
+ /// Reads options from environment variables.
+ /// Throws ArgumentOutOfRangeException for invalid arguments.
+ ///
+ 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();
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/Autobuilder.cs b/csharp/extractor/Semmle.Autobuild/Autobuilder.cs
new file mode 100644
index 000000000000..3edcb802ffed
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/Autobuilder.cs
@@ -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
+{
+ ///
+ /// A build rule analyses the files in "builder" and outputs a build script.
+ ///
+ interface IBuildRule
+ {
+ ///
+ /// Analyse the files and produce a build script.
+ ///
+ /// The files and options relating to the build.
+ BuildScript Analyse(Autobuilder builder);
+ }
+
+ ///
+ /// 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.
+ ///
+ public class Autobuilder
+ {
+ ///
+ /// Full file paths of files found in the project directory.
+ ///
+ public IEnumerable Paths => pathsLazy.Value;
+ readonly Lazy> pathsLazy;
+
+ ///
+ /// Gets a list of paths matching a set of extensions
+ /// (including the ".").
+ ///
+ /// The extensions to find.
+ /// The files matching the extension.
+ public IEnumerable GetExtensions(params string[] extensions) =>
+ Paths.Where(p => extensions.Contains(Path.GetExtension(p)));
+
+ ///
+ /// Gets all paths matching a particular filename.
+ ///
+ /// The filename to find.
+ /// Possibly empty sequence of paths with the given filename.
+ public IEnumerable GetFilename(string name) => Paths.Where(p => Path.GetFileName(p) == name);
+
+ ///
+ /// Holds if a given path, relative to the root of the source directory
+ /// was found.
+ ///
+ /// The relative path.
+ /// True iff the path was found.
+ public bool HasRelativePath(string path) => HasPath(Actions.PathCombine(RootDirectory, path));
+
+ ///
+ /// List of solution files to build.
+ ///
+ public IList SolutionsToBuild => solutionsToBuildLazy.Value;
+ readonly Lazy> solutionsToBuildLazy;
+
+ ///
+ /// Holds if a given path was found.
+ ///
+ /// The path of the file.
+ /// True iff the path was found.
+ public bool HasPath(string path) => Paths.Any(p => path == p);
+
+ void FindFiles(string dir, int depth, IList 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);
+ }
+ }
+ }
+
+ ///
+ /// The root of the source directory.
+ ///
+ string RootDirectory => Options.RootDirectory;
+
+ ///
+ /// Gets the supplied build configuration.
+ ///
+ public AutobuildOptions Options { get; }
+
+ ///
+ /// The set of build actions used during the autobuilder.
+ /// Could be real system operations, or a stub for testing.
+ ///
+ public IBuildActions Actions { get; }
+
+ ///
+ /// Find all the relevant files and picks the best
+ /// solution file and tools.
+ ///
+ /// The command line options.
+ public Autobuilder(IBuildActions actions, AutobuildOptions options)
+ {
+ Actions = actions;
+ Options = options;
+
+ pathsLazy = new Lazy>(() =>
+ {
+ var files = new List();
+ 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>(() =>
+ {
+ if (options.Solution.Any())
+ {
+ var ret = new List();
+ 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(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);
+
+ ///
+ /// Log a given build event to the console.
+ ///
+ /// The format string.
+ /// Inserts to the format string.
+ public void Log(Severity severity, string format, params object[] args)
+ {
+ logger.Log(severity, format, args);
+ }
+
+ ///
+ /// Attempt to build this project.
+ ///
+ /// The exit code, 0 for success and non-zero for failures.
+ 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);
+ }
+
+ ///
+ /// Returns the build script to use for this project.
+ ///
+ public BuildScript GetBuildScript()
+ {
+ var isCSharp = Options.Language == Language.CSharp;
+ return isCSharp ? GetCSharpBuildScript() : GetCppBuildScript();
+ }
+
+ BuildScript GetCSharpBuildScript()
+ {
+ ///
+ /// A script that checks that the C# extractor has been executed.
+ ///
+ 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);
+
+ ///
+ /// 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.
+ ///
+ 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));
+ }
+
+ ///
+ /// Gets the build strategy that the autobuilder should apply, based on the
+ /// options in the `lgtm.yml` file.
+ ///
+ 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;
+ });
+
+ ///
+ /// Value of SEMMLE_DIST environment variable.
+ ///
+ public string SemmleDist { get; private set; }
+
+ ///
+ /// Value of SEMMLE_JAVA_HOME environment variable.
+ ///
+ public string SemmleJavaHome { get; private set; }
+
+ ///
+ /// Value of SEMMLE_PLATFORM_TOOLS environment variable.
+ ///
+ public string SemmlePlatformTools { get; private set; }
+
+ ///
+ /// The absolute path of the odasa executable.
+ ///
+ public string Odasa => SemmleDist == null ? null : Actions.PathCombine(SemmleDist, "tools", "odasa");
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/BuildActions.cs b/csharp/extractor/Semmle.Autobuild/BuildActions.cs
new file mode 100644
index 000000000000..906d6701c1a8
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/BuildActions.cs
@@ -0,0 +1,176 @@
+using Semmle.Util;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+
+namespace Semmle.Autobuild
+{
+ ///
+ /// Wrapper around system calls so that the build scripts can be unit-tested.
+ ///
+ public interface IBuildActions
+ {
+ ///
+ /// Runs a process and captures its output.
+ ///
+ /// The exe to run.
+ /// The other command line arguments.
+ /// The working directory (null
for current directory).
+ /// Additional environment variables.
+ /// The lines of stdout.
+ /// The process exit code.
+ int RunProcess(string exe, string args, string workingDirectory, IDictionary env, out IList stdOut);
+
+ ///
+ /// Runs a process but does not capture its output.
+ ///
+ /// The exe to run.
+ /// The other command line arguments.
+ /// The working directory (null
for current directory).
+ /// Additional environment variables.
+ /// The process exit code.
+ int RunProcess(string exe, string args, string workingDirectory, IDictionary env);
+
+ ///
+ /// Tests whether a file exists, File.Exists().
+ ///
+ /// The filename.
+ /// True iff the file exists.
+ bool FileExists(string file);
+
+ ///
+ /// Tests whether a directory exists, Directory.Exists().
+ ///
+ /// The directory name.
+ /// True iff the directory exists.
+ bool DirectoryExists(string dir);
+
+ ///
+ /// Deletes a file, File.Delete().
+ ///
+ /// The filename.
+ void FileDelete(string file);
+
+ ///
+ /// Deletes a directory, Directory.Delete().
+ ///
+ void DirectoryDelete(string dir, bool recursive);
+
+ ///
+ /// Gets an environment variable, Environment.GetEnvironmentVariable().
+ ///
+ /// The name of the variable.
+ /// The string value, or null if the variable is not defined.
+ string GetEnvironmentVariable(string name);
+
+ ///
+ /// Gets the current directory, Directory.GetCurrentDirectory().
+ ///
+ /// The current directory.
+ string GetCurrentDirectory();
+
+ ///
+ /// Enumerates files in a directory, Directory.EnumerateFiles().
+ ///
+ /// The directory to enumerate.
+ /// A list of filenames, or an empty list.
+ IEnumerable EnumerateFiles(string dir);
+
+ ///
+ /// Enumerates the directories in a directory, Directory.EnumerateDirectories().
+ ///
+ /// The directory to enumerate.
+ /// List of subdirectories, or empty list.
+ IEnumerable EnumerateDirectories(string dir);
+
+ ///
+ /// True if we are running on Windows.
+ ///
+ bool IsWindows();
+
+ ///
+ /// Combine path segments, Path.Combine().
+ ///
+ /// The parts of the path.
+ /// The combined path.
+ string PathCombine(params string[] parts);
+
+ ///
+ /// Writes contents to file, File.WriteAllText().
+ ///
+ /// The filename.
+ /// The text.
+ void WriteAllText(string filename, string contents);
+ }
+
+ ///
+ /// An implementation of IBuildActions that actually performs the requested operations.
+ ///
+ 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 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 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 environment, out IList 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 IBuildActions.EnumerateFiles(string dir) => Directory.EnumerateFiles(dir);
+
+ IEnumerable 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();
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/BuildCommandAutoRule.cs b/csharp/extractor/Semmle.Autobuild/BuildCommandAutoRule.cs
new file mode 100644
index 000000000000..627651eaefe9
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/BuildCommandAutoRule.cs
@@ -0,0 +1,61 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Semmle.Util;
+using Semmle.Util.Logging;
+
+namespace Semmle.Autobuild
+{
+ ///
+ /// Auto-detection of build scripts.
+ ///
+ class BuildCommandAutoRule : IBuildRule
+ {
+ readonly IEnumerable winExtensions = new List {
+ ".bat",
+ ".cmd",
+ ".exe"
+ };
+
+ readonly IEnumerable linuxExtensions = new List {
+ "",
+ ".sh"
+ };
+
+ readonly IEnumerable buildScripts = new List {
+ "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;
+ });
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/BuildCommandRule.cs b/csharp/extractor/Semmle.Autobuild/BuildCommandRule.cs
new file mode 100644
index 000000000000..2dd3a1442223
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/BuildCommandRule.cs
@@ -0,0 +1,28 @@
+namespace Semmle.Autobuild
+{
+ ///
+ /// Execute the build_command rule.
+ ///
+ 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;
+ });
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/BuildScript.cs b/csharp/extractor/Semmle.Autobuild/BuildScript.cs
new file mode 100644
index 000000000000..d36e98a0db53
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/BuildScript.cs
@@ -0,0 +1,274 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using Semmle.Util;
+
+namespace Semmle.Autobuild
+{
+ ///
+ /// A build script.
+ ///
+ public abstract class BuildScript
+ {
+ ///
+ /// Run this build script.
+ ///
+ ///
+ /// The interface used to implement the build actions.
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ /// 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.
+ ///
+ /// The exit code from this build script.
+ public abstract int Run(IBuildActions actions, Action startCallback, Action exitCallBack);
+
+ ///
+ /// Run this build command.
+ ///
+ ///
+ /// The interface used to implement the build actions.
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ /// 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.
+ ///
+ /// Contents of standard out.
+ /// The exit code from this build script.
+ public abstract int Run(IBuildActions actions, Action startCallback, Action exitCallBack, out IList stdout);
+
+ class BuildCommand : BuildScript
+ {
+ readonly string exe, arguments, workingDirectory;
+ readonly IDictionary environment;
+
+ ///
+ /// Create a simple build command.
+ ///
+ /// The executable to run.
+ /// The arguments to the executable, or null.
+ /// The working directory (null
for current directory).
+ /// Additional environment variables.
+ public BuildCommand(string exe, string argumentsOpt, string workingDirectory = null, IDictionary 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 startCallback, Action 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 startCallback, Action exitCallBack, out IList 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 func;
+ public ReturnBuildCommand(Func func)
+ {
+ this.func = func;
+ }
+
+ public override int Run(IBuildActions actions, Action startCallback, Action exitCallBack) => func(actions);
+
+ public override int Run(IBuildActions actions, Action startCallback, Action exitCallBack, out IList stdout)
+ {
+ stdout = new string[0];
+ return func(actions);
+ }
+ }
+
+ class BindBuildScript : BuildScript
+ {
+ readonly BuildScript s1;
+ readonly Func, int, BuildScript> s2a;
+ readonly Func s2b;
+ public BindBuildScript(BuildScript s1, Func, int, BuildScript> s2)
+ {
+ this.s1 = s1;
+ this.s2a = s2;
+ }
+
+ public BindBuildScript(BuildScript s1, Func s2)
+ {
+ this.s1 = s1;
+ this.s2b = s2;
+ }
+
+ public override int Run(IBuildActions actions, Action startCallback, Action 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 startCallback, Action exitCallBack, out IList 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();
+ @out.AddRange(stdout1);
+ @out.AddRange(stdout2);
+ stdout = @out;
+ return ret2;
+ }
+ }
+
+ ///
+ /// Creates a simple build script that runs the specified exe.
+ ///
+ /// The arguments to the executable, or null.
+ /// The working directory (null
for current directory).
+ /// Additional environment variables.
+ public static BuildScript Create(string exe, string argumentsOpt, string workingDirectory, IDictionary environment) =>
+ new BuildCommand(exe, argumentsOpt, workingDirectory, environment);
+
+ ///
+ /// Creates a simple build script that runs the specified function.
+ ///
+ public static BuildScript Create(Func func) =>
+ new ReturnBuildCommand(func);
+
+ ///
+ /// Creates a build script that runs , followed by running the script
+ /// produced by on the exit code from .
+ ///
+ public static BuildScript Bind(BuildScript s1, Func s2) =>
+ new BindBuildScript(s1, s2);
+
+ ///
+ /// Creates a build script that runs , followed by running the script
+ /// produced by on the exit code and standard output from
+ /// .
+ ///
+ public static BuildScript Bind(BuildScript s1, Func, int, BuildScript> s2) =>
+ new BindBuildScript(s1, s2);
+
+ const int SuccessCode = 0;
+ ///
+ /// The empty build script that always returns exit code 0.
+ ///
+ public static readonly BuildScript Success = Create(actions => SuccessCode);
+
+ const int FailureCode = 1;
+ ///
+ /// The empty build script that always returns exit code 1.
+ ///
+ 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 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 s2) =>
+ new BindBuildScript(s1, ret1 => Succeeded(ret1) ? Success : s2());
+
+ ///
+ /// Creates a build script that attempts to run the build script ,
+ /// always returning a successful exit code.
+ ///
+ public static BuildScript Try(BuildScript s) => s | Success;
+
+ ///
+ /// Creates a build script that deletes the given directory.
+ ///
+ 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;
+ });
+
+ ///
+ /// Creates a build script that deletes the given file.
+ ///
+ 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;
+ });
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/BuildTools.cs b/csharp/extractor/Semmle.Autobuild/BuildTools.cs
new file mode 100644
index 000000000000..ca9285d2c9c1
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/BuildTools.cs
@@ -0,0 +1,99 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Semmle.Autobuild
+{
+ ///
+ /// A BAT file used to initialise the appropriate
+ /// Visual Studio version/platform.
+ ///
+ 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;
+ }
+ };
+
+ ///
+ /// Collection of available Visual Studio build tools.
+ ///
+ public static class BuildTools
+ {
+ public static IEnumerable 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");
+ }
+
+ ///
+ /// Enumerates all available tools.
+ ///
+ public static IEnumerable VcVarsAllBatFiles(IBuildActions actions) =>
+ GetCandidateVcVarsFiles(actions).Where(v => actions.FileExists(v.Path));
+
+ ///
+ /// Finds a VcVars file that provides a compatible environment for the given solution.
+ ///
+ /// The solution file.
+ /// A compatible file, or throws an exception.
+ public static VcVarsBatFile FindCompatibleVcVars(IBuildActions actions, ISolution sln) =>
+ FindCompatibleVcVars(actions, sln.ToolsVersion.Major);
+
+ ///
+ /// Finds a VcVars that provides a compatible environment for the given tools version.
+ ///
+ /// The tools version.
+ /// A compatible file, or null.
+ 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();
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/CommandBuilder.cs b/csharp/extractor/Semmle.Autobuild/CommandBuilder.cs
new file mode 100644
index 000000000000..273cc52b0362
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/CommandBuilder.cs
@@ -0,0 +1,195 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Semmle.Autobuild
+{
+ ///
+ /// Utility to construct a build command.
+ ///
+ class CommandBuilder
+ {
+ enum EscapeMode { Process, Cmd };
+
+ readonly StringBuilder arguments;
+ bool firstCommand;
+ string executable;
+ readonly EscapeMode escapingMode;
+ readonly string workingDirectory;
+ readonly IDictionary environment;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The working directory (null
for current directory).
+ /// Additional environment variables.
+ public CommandBuilder(IBuildActions actions, string workingDirectory = null, IDictionary 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;
+ }
+
+ ///
+ /// Perform odasa index on a given command or BAT file.
+ ///
+ /// The odasa executable.
+ /// The command to run.
+ /// Additional arguments.
+ /// this for chaining calls.
+ 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 = { '(', ')', '%', '!', '^', '\"', '<', '>', '&', '|' };
+
+ ///
+ /// Appends the given argument to the command line.
+ ///
+ /// The argument to append.
+ /// Whether to always quote the argument.
+ /// Whether to escape for cmd.exe
+ ///
+ ///
+ /// This implementation is copied from
+ /// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
+ ///
+ 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;
+ }
+
+ ///
+ /// Returns a build script that contains just this command.
+ ///
+ public BuildScript Script => BuildScript.Create(executable, arguments.ToString(), workingDirectory, environment);
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/DotNetRule.cs b/csharp/extractor/Semmle.Autobuild/DotNetRule.cs
new file mode 100644
index 000000000000..a862a4f1a1b7
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/DotNetRule.cs
@@ -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
+{
+ ///
+ /// A build rule where the build command is of the form "dotnet build".
+ /// Currently unused because the tracer does not work with dotnet.
+ ///
+ 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;
+ });
+ }
+
+ ///
+ /// Returns a script that attempts to download relevant version(s) of the
+ /// .NET Core SDK, followed by running the script generated by .
+ ///
+ /// The first element DotNetPath
of the argument to
+ /// is the path where .NET Core was installed, and the second element Environment
+ /// is any additional required environment variables. The tuple argument is null
+ /// when the installation failed.
+ ///
+ public static BuildScript WithDotNet(Autobuilder builder, Func<(string DotNetPath, IDictionary 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{
+ { "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);
+ });
+ }
+
+ ///
+ /// Returns a script for downloading relevant versions of the
+ /// .NET Core SDK. The SDK(s) will be installed at installDir
+ /// (provided that the script succeeds).
+ ///
+ 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;
+ }
+
+ ///
+ /// 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.
+ ///
+ 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 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 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 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 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;
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/Language.cs b/csharp/extractor/Semmle.Autobuild/Language.cs
new file mode 100644
index 000000000000..6f8d73b2a471
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/Language.cs
@@ -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#";
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/MsBuildRule.cs b/csharp/extractor/Semmle.Autobuild/MsBuildRule.cs
new file mode 100644
index 000000000000..cb7eae2c23b1
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/MsBuildRule.cs
@@ -0,0 +1,118 @@
+using Semmle.Util.Logging;
+using System.IO;
+using System.Linq;
+
+namespace Semmle.Autobuild
+{
+ ///
+ /// A build rule using msbuild.
+ ///
+ class MsBuildRule : IBuildRule
+ {
+ ///
+ /// The name of the msbuild command.
+ ///
+ 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;
+ }
+
+ ///
+ /// Gets the BAT file used to initialize the appropriate Visual Studio
+ /// version/platform, as specified by the `vstools_version` property in
+ /// lgtm.yml.
+ ///
+ /// Returns null
when no version is specified.
+ ///
+ 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;
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/Program.cs b/csharp/extractor/Semmle.Autobuild/Program.cs
new file mode 100644
index 000000000000..c4542864a091
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/Program.cs
@@ -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();
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/Project.cs b/csharp/extractor/Semmle.Autobuild/Project.cs
new file mode 100644
index 000000000000..e0a1ee8386dc
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/Project.cs
@@ -0,0 +1,69 @@
+using System;
+using System.IO;
+using System.Xml;
+using Semmle.Util.Logging;
+
+namespace Semmle.Autobuild
+{
+ ///
+ /// 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.
+ ///
+ public class Project
+ {
+ ///
+ /// Holds if this project is for .Net core.
+ ///
+ 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;
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/Properties/AssemblyInfo.cs b/csharp/extractor/Semmle.Autobuild/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000000..e3da7ca22e9e
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/Properties/AssemblyInfo.cs
@@ -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")]
diff --git a/csharp/extractor/Semmle.Autobuild/Semmle.Autobuild.csproj b/csharp/extractor/Semmle.Autobuild/Semmle.Autobuild.csproj
new file mode 100644
index 000000000000..8a9d272169c5
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/Semmle.Autobuild.csproj
@@ -0,0 +1,27 @@
+
+
+
+ netcoreapp2.0
+ Semmle.Autobuild
+ Semmle.Autobuild
+
+ Exe
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/csharp/extractor/Semmle.Autobuild/Solution.cs b/csharp/extractor/Semmle.Autobuild/Solution.cs
new file mode 100644
index 000000000000..66bbff777ac8
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/Solution.cs
@@ -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
+{
+ ///
+ /// A solution file, extension .sln.
+ ///
+ public interface ISolution
+ {
+ ///
+ /// List of C# or C++ projects contained in the solution.
+ /// (There could be other project types as well - these are ignored.)
+ ///
+
+ IEnumerable Projects { get; }
+
+ ///
+ /// Solution configurations.
+ ///
+ IEnumerable Configurations { get; }
+
+ ///
+ /// The default configuration name, e.g. "Release"
+ ///
+ string DefaultConfigurationName { get; }
+
+ ///
+ /// The default platform name, e.g. "x86"
+ ///
+ string DefaultPlatformName { get; }
+
+ ///
+ /// The path of the solution file.
+ ///
+ string Path { get; }
+
+ ///
+ /// The number of C# or C++ projects.
+ ///
+ int ProjectCount { get; }
+
+ ///
+ /// 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.
+ ///
+ Version ToolsVersion { get; }
+ }
+
+ ///
+ /// A solution file on the filesystem, read using Microsoft.Build.
+ ///
+ class Solution : ISolution
+ {
+ readonly SolutionFile solution;
+
+ public IEnumerable Projects { get; private set; }
+
+ public IEnumerable Configurations =>
+ solution == null ? Enumerable.Empty() : 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 ToolsVersions => Projects.Where(p => p.ValidToolsVersion).Select(p => p.ToolsVersion);
+
+ public Version ToolsVersion => ToolsVersions.Any() ? ToolsVersions.Max() : new Version();
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/StandaloneBuildRule.cs b/csharp/extractor/Semmle.Autobuild/StandaloneBuildRule.cs
new file mode 100644
index 000000000000..3fafa84e454b
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/StandaloneBuildRule.cs
@@ -0,0 +1,46 @@
+using System.IO;
+
+namespace Semmle.Autobuild
+{
+ ///
+ /// Build using standalone extraction.
+ ///
+ 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;
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Autobuild/XmlBuildRule.cs b/csharp/extractor/Semmle.Autobuild/XmlBuildRule.cs
new file mode 100644
index 000000000000..6b0a5b1cbb19
--- /dev/null
+++ b/csharp/extractor/Semmle.Autobuild/XmlBuildRule.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+
+namespace Semmle.Autobuild
+{
+ ///
+ /// XML extraction.
+ ///
+ 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;
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL.Driver/ExtractorOptions.cs b/csharp/extractor/Semmle.Extraction.CIL.Driver/ExtractorOptions.cs
new file mode 100644
index 000000000000..0e9a8f9e116f
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL.Driver/ExtractorOptions.cs
@@ -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
+{
+ ///
+ /// Information about a single assembly.
+ /// In particular, provides references between assemblies.
+ ///
+ 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;
+ }
+
+ ///
+ /// Helper to manage a collection of assemblies.
+ /// Resolves references between assemblies and determines which
+ /// additional assemblies need to be extracted.
+ ///
+ class AssemblyList
+ {
+ class AssemblyNameComparer : IEqualityComparer
+ {
+ bool IEqualityComparer.Equals(AssemblyName x, AssemblyName y) =>
+ x.Name == y.Name && x.Version == y.Version;
+
+ int IEqualityComparer.GetHashCode(AssemblyName obj) =>
+ obj.Name.GetHashCode() + 7 * obj.Version.GetHashCode();
+ }
+
+ readonly Dictionary assembliesRead = new Dictionary(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 AssembliesToExtract => assembliesRead.Values.Where(info => info.extract);
+
+ IEnumerable AssembliesToReference => AssembliesToExtract.SelectMany(info => info.references);
+
+ public void ResolveReferences()
+ {
+ var assembliesToReference = new Stack(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 filesAnalyzed = new HashSet();
+ public readonly HashSet missingReferences = new HashSet();
+ }
+
+ ///
+ /// Parses the command line and collates a list of DLLs/EXEs to extract.
+ ///
+ 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 AssembliesToExtract { get; private set; }
+
+ ///
+ /// 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.
+ ///
+ public IEnumerable 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;
+ }
+
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL.Driver/Program.cs b/csharp/extractor/Semmle.Extraction.CIL.Driver/Program.cs
new file mode 100644
index 000000000000..df17f5a187ef
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL.Driver/Program.cs
@@ -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(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);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL.Driver/Properties/AssemblyInfo.cs b/csharp/extractor/Semmle.Extraction.CIL.Driver/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000000..56f7f94c1437
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL.Driver/Properties/AssemblyInfo.cs
@@ -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")]
diff --git a/csharp/extractor/Semmle.Extraction.CIL.Driver/Semmle.Extraction.CIL.Driver.csproj b/csharp/extractor/Semmle.Extraction.CIL.Driver/Semmle.Extraction.CIL.Driver.csproj
new file mode 100644
index 000000000000..b0e1a525490e
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL.Driver/Semmle.Extraction.CIL.Driver.csproj
@@ -0,0 +1,21 @@
+
+
+
+ Exe
+ netcoreapp2.0
+ Semmle.Extraction.CIL.Driver
+ Semmle.Extraction.CIL.Driver
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/csharp/extractor/Semmle.Extraction.CIL/CachedFunction.cs b/csharp/extractor/Semmle.Extraction.CIL/CachedFunction.cs
new file mode 100644
index 000000000000..3bbc386a691e
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/CachedFunction.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+
+namespace Semmle.Extraction.CIL
+{
+ ///
+ /// A factory and a cache for mapping source entities to target entities.
+ /// Could be considered as a memoizer.
+ ///
+ /// The type of the source.
+ /// The type of the generated object.
+ public class CachedFunction
+ {
+ readonly Func generator;
+ readonly Dictionary cache;
+
+ ///
+ /// Initializes the factory with a given mapping.
+ ///
+ /// The mapping.
+ public CachedFunction(Func g)
+ {
+ generator = g;
+ cache = new Dictionary();
+ }
+
+ ///
+ /// Gets the target for a given source.
+ /// Create it if it does not exist.
+ ///
+ /// The source object.
+ /// The created object.
+ public TargetType this[SrcType src]
+ {
+ get
+ {
+ TargetType result;
+ if (!cache.TryGetValue(src, out result))
+ {
+ result = generator(src);
+ cache[src] = result;
+ }
+ return result;
+ }
+ }
+ }
+
+ ///
+ /// A factory for mapping a pair of source entities to a target entity.
+ ///
+ /// Source entity type 1.
+ /// Source entity type 2.
+ /// The target type.
+ public class CachedFunction
+ {
+ readonly CachedFunction<(Src1, Src2), Target> factory;
+
+ ///
+ /// Initializes the factory with a given mapping.
+ ///
+ /// The mapping.
+ public CachedFunction(Func g)
+ {
+ factory = new CachedFunction<(Src1, Src2), Target>(p => g(p.Item1, p.Item2));
+ }
+
+ public Target this[Src1 s1, Src2 s2] => factory[(s1, s2)];
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Context.cs b/csharp/extractor/Semmle.Extraction.CIL/Context.cs
new file mode 100644
index 000000000000..dc2801e5bc72
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Context.cs
@@ -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
+{
+ ///
+ /// Extraction context for CIL extraction.
+ /// Adds additional context that is specific for CIL extraction.
+ /// One context = one DLL/EXE.
+ ///
+ 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(() => Populate(new Entities.Namespace(this, GetId(""), null)));
+ systemNamespace = new Lazy(() => Populate(new Entities.Namespace(this, "System")));
+ genericHandleFactory = new CachedFunction(CreateGenericHandle);
+ namespaceFactory = new CachedFunction(n => CreateNamespace(mdReader.GetString(n)));
+ namespaceDefinitionFactory = new CachedFunction(CreateNamespace);
+ sourceFiles = new CachedFunction(path => new Entities.PdbSourceFile(this, path));
+ folders = new CachedFunction(path => new Entities.Folder(this, path));
+ sourceLocations = new CachedFunction(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();
+ }
+
+ ///
+ /// Extract the contents of a given entity.
+ ///
+ /// The entity to extract.
+ public void Extract(IExtractedEntity entity)
+ {
+ foreach (var content in entity.Contents)
+ {
+ content.Extract(this);
+ }
+ }
+
+ public readonly Id AssemblyPrefix;
+
+ public readonly Entities.TypeSignatureDecoder TypeSignatureDecoder;
+
+ ///
+ /// A type used to signify something we can't handle yet.
+ /// Specifically, function pointers (used in C++).
+ ///
+ public Entities.Type ErrorType
+ {
+ get
+ {
+ var errorType = new Entities.ErrorType(this);
+ Populate(errorType);
+ return errorType;
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The handle of the method.
+ /// The debugging information, or null if the information could not be located.
+ public PDB.IMethod GetMethodDebugInformation(MethodDefinitionHandle handle)
+ {
+ return pdb == null ? null : pdb.GetMethod(handle.ToDebugInformationHandle());
+ }
+ }
+
+ ///
+ /// When we decode a type/method signature, we need access to
+ /// generic parameters.
+ ///
+ public abstract class GenericContext
+ {
+ public Context cx;
+
+ public GenericContext(Context cx)
+ {
+ this.cx = cx;
+ }
+
+ ///
+ /// The list of generic type parameters.
+ ///
+ public abstract IEnumerable TypeParameters { get; }
+
+ ///
+ /// The list of generic method parameters.
+ ///
+ public abstract IEnumerable MethodParameters { get; }
+
+ ///
+ /// Gets the `p`th type parameter.
+ ///
+ /// The index of the parameter.
+ ///
+ /// For constructed types, the supplied type.
+ /// For unbound types, the type parameter.
+ ///
+ public Entities.Type GetGenericTypeParameter(int p)
+ {
+ return TypeParameters.ElementAt(p);
+ }
+
+ ///
+ /// Gets the `p`th method type parameter.
+ ///
+ /// The index of the parameter.
+ ///
+ /// For constructed types, the supplied type.
+ /// For unbound types, the type parameter.
+ ///
+ public Entities.Type GetGenericMethodParameter(int p)
+ {
+ return MethodParameters.ElementAt(p);
+ }
+ }
+
+ ///
+ /// A generic context which does not contain any type parameters.
+ ///
+ public class EmptyContext : GenericContext
+ {
+ public EmptyContext(Context cx) : base(cx)
+ {
+ }
+
+ public override IEnumerable TypeParameters { get { yield break; } }
+
+ public override IEnumerable MethodParameters { get { yield break; } }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Assembly.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Assembly.cs
new file mode 100644
index 000000000000..31745a434f67
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Assembly.cs
@@ -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
+ {
+ }
+
+ ///
+ /// An assembly to extract.
+ ///
+ 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 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();
+ }
+ }
+
+ ///
+ /// Main entry point to the CIL extractor.
+ /// Call this to extract a given assembly.
+ ///
+ /// The trap layout.
+ /// The full path of the assembly to extract.
+ /// The logger.
+ /// True to overwrite existing trap file.
+ /// Whether to extract PDBs.
+ /// The path of the trap file.
+ /// Whether the file was extracted (false=cached).
+ 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));
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Attribute.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Attribute.cs
new file mode 100644
index 000000000000..774de6cf145a
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Attribute.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ ///
+ /// A CIL attribute.
+ ///
+ interface IAttribute : IExtractedEntity
+ {
+ }
+
+ ///
+ /// Entity representing a CIL attribute.
+ ///
+ 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 Contents
+ {
+ get
+ {
+ var constructor = (Method)cx.Create(attrib.Constructor);
+ yield return constructor;
+
+ yield return Tuples.cil_attribute(this, @object, constructor);
+
+ CustomAttributeValue 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 Populate(Context cx, IEntity @object, CustomAttributeHandleCollection attributes)
+ {
+ foreach (var attrib in attributes)
+ {
+ yield return new Attribute(cx, @object, attrib);
+ }
+ }
+ }
+
+ ///
+ /// Helper class to decode the attribute structure.
+ /// Note that there are some unhandled cases that should be fixed in due course.
+ ///
+ class CustomAttributeDecoder : ICustomAttributeTypeProvider
+ {
+ 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; // ??
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Event.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Event.cs
new file mode 100644
index 000000000000..009afc1b1e9f
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Event.cs
@@ -0,0 +1,67 @@
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ ///
+ /// An event.
+ ///
+ interface IEvent : ILabelledEntity
+ {
+ }
+
+ ///
+ /// An event entity.
+ ///
+ 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 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;
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/ExceptionRegion.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/ExceptionRegion.cs
new file mode 100644
index 000000000000..57035d993af0
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/ExceptionRegion.cs
@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ interface IExceptionRegion : IExtractedEntity
+ {
+ }
+
+ ///
+ /// An exception region entity.
+ ///
+ class ExceptionRegion : UnlabelledEntity, IExceptionRegion
+ {
+ readonly GenericContext gc;
+ readonly MethodImplementation method;
+ readonly int index;
+ readonly System.Reflection.Metadata.ExceptionRegion r;
+ readonly Dictionary jump_table;
+
+ public ExceptionRegion(GenericContext gc, MethodImplementation method, int index, System.Reflection.Metadata.ExceptionRegion r, Dictionary 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 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);
+ }
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Field.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Field.cs
new file mode 100644
index 000000000000..64367ca9ae0c
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Field.cs
@@ -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
+{
+ ///
+ /// An entity represting a member.
+ /// Used to type tuples correctly.
+ ///
+ interface IMember : ILabelledEntity
+ {
+ }
+
+ ///
+ /// An entity representing a field.
+ ///
+ interface IField : IMember
+ {
+ }
+
+ ///
+ /// An entity representing a field.
+ ///
+ 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 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 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 TypeParameters => throw new NotImplementedException();
+
+ public override IEnumerable 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 TypeParameters => gc.TypeParameters.Concat(declType.TypeParameters);
+
+ public override IEnumerable MethodParameters => gc.MethodParameters.Concat(declType.MethodParameters);
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/File.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/File.cs
new file mode 100644
index 000000000000..de8f890312a1
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/File.cs
@@ -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 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 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);
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Folder.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Folder.cs
new file mode 100644
index 000000000000..0ed1b51dd995
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Folder.cs
@@ -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 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;
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Instruction.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Instruction.cs
new file mode 100644
index 000000000000..de6640235f32
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Instruction.cs
@@ -0,0 +1,486 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ ///
+ /// A CIL instruction.
+ ///
+ interface IInstruction : IExtractedEntity
+ {
+ ///
+ /// Gets the extraction products for branches.
+ ///
+ /// The map from offset to instruction.
+ /// The extraction products.
+ IEnumerable JumpContents(Dictionary jump_table);
+ }
+
+ ///
+ /// A CIL instruction.
+ ///
+ class Instruction : UnlabelledEntity, IInstruction
+ {
+ ///
+ /// The additional data following the opcode, if any.
+ ///
+ 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
+ }
+
+ ///
+ /// For each Payload, how many additional bytes in the bytestream need to be read.
+ ///
+ 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 opPayload = new Dictionary()
+ {
+ { 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;
+
+ ///
+ /// The number of bytes of this instruction,
+ /// including the payload (if any).
+ ///
+ 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];
+
+ ///
+ /// Reads the instruction from a byte stream.
+ ///
+ /// The byte stream.
+ /// The offset of the instruction.
+ /// The index of this instruction in the callable.
+ 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 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 JumpContents(Dictionary 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 });
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/LocalVariable.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/LocalVariable.cs
new file mode 100644
index 000000000000..4e3fbf43662d
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/LocalVariable.cs
@@ -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 Contents
+ {
+ get
+ {
+ yield return type;
+ yield return Tuples.cil_local_variable(this, method, index, type);
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Method.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Method.cs
new file mode 100644
index 000000000000..306e2205c0bd
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Method.cs
@@ -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
+{
+ ///
+ /// A method entity.
+ ///
+ interface IMethod : IMember
+ {
+ }
+
+ ///
+ /// A method entity.
+ ///
+ abstract class Method : TypeContainer, IMethod
+ {
+ protected Method(GenericContext gc) : base(gc.cx)
+ {
+ this.gc = gc;
+ }
+
+ public override IEnumerable TypeParameters => gc.TypeParameters.Concat(declaringType.TypeParameters);
+
+ public override IEnumerable MethodParameters => genericParams == null ? Enumerable.Empty() : genericParams;
+
+ public int GenericParameterCount => signature.GenericParameterCount;
+
+ public virtual Method SourceDeclaration => this;
+
+ public abstract Type DeclaringType { get; }
+ public abstract string Name { get; }
+
+ public virtual IList LocalVariables => throw new NotImplementedException();
+ public IList 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 signature;
+ protected Id name;
+
+ static readonly StringId methodSuffix = new StringId(";cil-method");
+
+ public override Id IdSuffix => methodSuffix;
+
+ protected void PopulateParameters(IEnumerable parameterTypes)
+ {
+ Parameters = MakeParameters(parameterTypes).ToArray();
+ }
+
+ protected IEnumerable PopulateFlags
+ {
+ get
+ {
+ if (IsStatic)
+ yield return Tuples.cil_static(this);
+ }
+ }
+
+ public abstract bool IsStatic { get; }
+
+ private IEnumerable MakeParameters(IEnumerable 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));
+ }
+ }
+
+ ///
+ /// A method implementation entity.
+ ///
+ interface IMethodImplementation : IExtractedEntity
+ {
+ }
+
+ ///
+ /// A method implementation entity.
+ /// In the database, the same method could in principle have multiple implementations.
+ ///
+ class MethodImplementation : UnlabelledEntity, IMethodImplementation
+ {
+ readonly Method m;
+
+ public MethodImplementation(Method m) : base(m.cx)
+ {
+ this.m = m;
+ }
+
+ public override IEnumerable Contents
+ {
+ get
+ {
+ yield return Tuples.cil_method_implementation(this, m, cx.assembly);
+ }
+ }
+ }
+
+
+ ///
+ /// A definition method - a method defined in the current assembly.
+ ///
+ class DefinitionMethod : Method, IMember
+ {
+ readonly MethodDefinition md;
+ readonly PDB.IMethod methodDebugInformation;
+
+ LocalVariable[] locals;
+
+ public MethodImplementation Implementation { get; private set; }
+
+ public override IList 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);
+
+ ///
+ /// Holds if this method has bytecode.
+ ///
+ public bool HasBytecode => md.ImplAttributes == MethodImplAttributes.IL && md.RelativeVirtualAddress != 0;
+
+ public override IEnumerable 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();
+
+ 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 Decode(byte[] ilbytes, Dictionary 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 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;
+ }
+ }
+
+ ///
+ /// Display the instructions in the method in the debugger.
+ /// This is only used for debugging, not in the code itself.
+ ///
+ public IEnumerable 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;
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// This is a late-bound reference to a method.
+ ///
+ 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 TypeParameters => parent.TypeParameters.Concat(gc.TypeParameters);
+
+ public override IEnumerable 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);
+ }
+ }
+ }
+
+ ///
+ /// A constructed method.
+ ///
+ class MethodSpecificationMethod : Method
+ {
+ readonly MethodSpecification ms;
+ readonly Method unboundMethod;
+ readonly ImmutableArray 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 MethodParameters => typeParams;
+
+ public override IEnumerable Contents
+ {
+ get
+ {
+ MethodSignature 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]);
+ }
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Namespace.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Namespace.cs
new file mode 100644
index 000000000000..e34d68554e71
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Namespace.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using Semmle.Extraction.Entities;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ ///
+ /// A namespace.
+ ///
+ interface INamespace : ITypeContainer
+ {
+ }
+
+ ///
+ /// A namespace.
+ ///
+ 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 TypeParameters => throw new NotImplementedException();
+
+ public override IEnumerable 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 Contents
+ {
+ get
+ {
+ yield return Tuples.namespaces(this, Name.Value);
+ if (!IsGlobalNamespace)
+ yield return Tuples.parent_namespace(this, ParentNamespace);
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Parameter.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Parameter.cs
new file mode 100644
index 000000000000..b008ccc25109
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Parameter.cs
@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ ///
+ /// A parameter entity.
+ ///
+ interface IParameter : ILabelledEntity
+ {
+ }
+
+ ///
+ /// A parameter entity.
+ ///
+ 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 Contents
+ {
+ get
+ {
+ yield return Tuples.cil_parameter(this, method, index, type);
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Property.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Property.cs
new file mode 100644
index 000000000000..2adacd2e8046
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Property.cs
@@ -0,0 +1,64 @@
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+using System.Linq;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ ///
+ /// A property.
+ ///
+ interface IProperty : ILabelledEntity
+ {
+ }
+
+ ///
+ /// A property.
+ ///
+ 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 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;
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/SourceLocation.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/SourceLocation.cs
new file mode 100644
index 000000000000..885927890995
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/SourceLocation.cs
@@ -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 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;
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Type.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Type.cs
new file mode 100644
index 000000000000..ac5c436770f5
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Type.cs
@@ -0,0 +1,1285 @@
+using System;
+using Microsoft.CodeAnalysis;
+using System.Reflection.Metadata;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Collections.Generic;
+using System.Reflection;
+using Semmle.Util;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ ///
+ /// A type.
+ ///
+ interface IType : IEntity
+ {
+ }
+
+ ///
+ /// An array type.
+ ///
+ interface IArrayType : IType
+ {
+ }
+
+ ///
+ /// The CIL database type-kind.
+ ///
+ public enum CilTypeKind
+ {
+ ValueOrRefType,
+ TypeParameter,
+ Array,
+ Pointer
+ }
+
+ ///
+ /// A type container (namespace/types/method).
+ ///
+ interface ITypeContainer : ILabelledEntity
+ {
+ }
+
+ ///
+ /// Base class for all type containers (namespaces, types, methods).
+ ///
+ abstract public class TypeContainer : GenericContext, ITypeContainer
+ {
+ protected TypeContainer(Context cx) : base(cx)
+ {
+ this.cx = cx;
+ }
+
+ public virtual Label Label { get; set; }
+
+ public virtual IId Id { get { return ShortId + IdSuffix; } }
+
+ public Id ShortId { get; set; }
+ public abstract Id IdSuffix { get; }
+
+ Location IEntity.ReportingLocation => throw new NotImplementedException();
+
+ public void Extract(Context cx) { cx.Populate(this); }
+
+ public abstract IEnumerable Contents { get; }
+
+ public override string ToString()
+ {
+ return Id.ToString();
+ }
+
+ TrapStackBehaviour IEntity.TrapStackBehaviour => TrapStackBehaviour.NoLabel;
+ }
+
+ ///
+ /// A type.
+ ///
+ public abstract class Type : TypeContainer, IMember, IType
+ {
+ static readonly Id suffix = CIL.Id.Create(";cil-type");
+ public override Id IdSuffix => suffix;
+
+ ///
+ /// Find the method in this type matching the name and signature.
+ ///
+ /// The handle to the name.
+ ///
+ /// The handle to the signature. Note that comparing handles is a valid
+ /// shortcut to comparing the signature bytes since handles are unique.
+ ///
+ /// The method, or 'null' if not found or not supported.
+ internal virtual Method LookupMethod(StringHandle Name, BlobHandle signature)
+ {
+ return null;
+ }
+
+ public IEnumerable TypeArguments
+ {
+ get
+ {
+ if (ContainingType != null)
+ foreach (var t in ContainingType.TypeArguments)
+ yield return t;
+
+ foreach (var t in ThisTypeArguments)
+ yield return t;
+
+ }
+ }
+
+ public virtual IEnumerable ThisTypeArguments
+ {
+ get
+ {
+ yield break;
+ }
+ }
+
+ ///
+ /// Gets the assembly identifier of this type.
+ ///
+ public abstract Id AssemblyPrefix { get; }
+
+ ///
+ /// Gets the ID part to be used in a method.
+ ///
+ ///
+ /// Whether we should output the context prefix of type parameters.
+ /// (This is to avoid infinite recursion generating a method ID that returns a
+ /// type parameter.)
+ ///
+ public abstract Id MakeId(bool inContext);
+
+ public Id GetId(bool inContext)
+ {
+ return inContext ? MakeId(true) : ShortId;
+ }
+
+ protected Type(Context cx) : base(cx) { }
+
+ public abstract CilTypeKind Kind
+ {
+ get;
+ }
+
+ public virtual TypeContainer Parent => (TypeContainer)ContainingType ?? Namespace;
+
+ public override IEnumerable Contents
+ {
+ get
+ {
+ yield return Tuples.cil_type(this, Name.Value, Kind, Parent, SourceDeclaration);
+ }
+ }
+
+ ///
+ /// Whether this type is visible outside the current assembly.
+ ///
+ public virtual bool IsVisible => true;
+
+ public abstract Id Name { get; }
+
+ public abstract Namespace Namespace { get; }
+
+ public abstract Type ContainingType { get; }
+
+ public abstract Type Construct(IEnumerable typeArguments);
+
+ ///
+ /// The number of type arguments, or 0 if this isn't generic.
+ /// The containing type may also have type arguments.
+ ///
+ public abstract int ThisTypeParameters { get; }
+
+ ///
+ /// The total number of type parameters (including parent types).
+ /// This is used for internal consistency checking only.
+ ///
+ public int TotalTypeParametersCheck =>
+ ContainingType == null ? ThisTypeParameters : ThisTypeParameters + ContainingType.TotalTypeParametersCheck;
+
+ ///
+ /// Returns all bound/unbound generic arguments
+ /// of a constructed/unbound generic type.
+ ///
+ public virtual IEnumerable ThisGenericArguments
+ {
+ get
+ {
+ yield break;
+ }
+ }
+
+ public virtual IEnumerable GenericArguments
+ {
+ get
+ {
+ if (ContainingType != null)
+ foreach (var t in ContainingType.GenericArguments)
+ yield return t;
+ foreach (var t in ThisGenericArguments)
+ yield return t;
+ }
+ }
+
+ public virtual Type SourceDeclaration => this;
+
+ protected static readonly Id builtin = CIL.Id.Create("builtin:");
+
+ public Id PrimitiveTypeId => builtin + Name;
+
+ public bool IsPrimitiveType
+ {
+ get
+ {
+ if (ContainingType == null && Namespace.ShortId == cx.SystemNamespace.ShortId)
+ {
+ switch (Name.Value)
+ {
+ case "Boolean":
+ case "Object":
+ case "Byte":
+ case "SByte":
+ case "Int16":
+ case "UInt16":
+ case "Int32":
+ case "UInt32":
+ case "Int64":
+ case "UInt64":
+ case "Single":
+ case "Double":
+ case "String":
+ case "Void":
+ case "IntPtr":
+ case "UIntPtr":
+ case "Char":
+ case "TypedReference":
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+ }
+
+ ///
+ /// A type defined in the current assembly.
+ ///
+ public sealed class TypeDefinitionType : Type
+ {
+ readonly TypeDefinition td;
+
+ public TypeDefinitionType(Context cx, TypeDefinitionHandle handle) : base(cx)
+ {
+ td = cx.mdReader.GetTypeDefinition(handle);
+
+ declType =
+ td.GetDeclaringType().IsNil ? null :
+ (Type)cx.Create(td.GetDeclaringType());
+
+ ShortId = MakeId(false);
+
+ // Lazy because should happen during population.
+ typeParams = new Lazy>(MakeTypeParameters);
+ }
+
+ public override Id MakeId(bool inContext)
+ {
+ if (IsPrimitiveType) return PrimitiveTypeId;
+
+ var name = cx.GetId(td.Name);
+
+ Id l;
+ if (ContainingType != null)
+ {
+ l = ContainingType.GetId(inContext) + cx.Dot;
+ }
+ else
+ {
+ l = AssemblyPrefix;
+
+ var ns = Namespace;
+ if (!ns.IsGlobalNamespace)
+ {
+ l = l + ns.ShortId + cx.Dot;
+ }
+ }
+
+ return l + name;
+ }
+
+ public override Id Name
+ {
+ get
+ {
+ var name = cx.GetId(td.Name);
+ var tick = name.Value.IndexOf('`');
+ return tick == -1 ? name : cx.GetId(name.Value.Substring(0, tick));
+ }
+ }
+
+ public override Namespace Namespace => cx.Create(td.NamespaceDefinition);
+
+ readonly Type declType;
+
+ public override Type ContainingType => declType;
+
+ public override int ThisTypeParameters
+ {
+ get
+ {
+ var containingType = td.GetDeclaringType();
+ var parentTypeParameters = containingType.IsNil ? 0 :
+ cx.mdReader.GetTypeDefinition(containingType).GetGenericParameters().Count;
+
+ return td.GetGenericParameters().Count - parentTypeParameters;
+ }
+ }
+
+ public override CilTypeKind Kind => CilTypeKind.ValueOrRefType;
+
+ public override Type Construct(IEnumerable typeArguments)
+ {
+ return cx.Populate(new ConstructedType(cx, this, typeArguments));
+ }
+
+ public override Id AssemblyPrefix
+ {
+ get
+ {
+ var ct = ContainingType;
+ return ct != null ? ct.AssemblyPrefix : IsPrimitiveType ? builtin : cx.AssemblyPrefix;
+ }
+ }
+
+ IEnumerable MakeTypeParameters()
+ {
+ if (ThisTypeParameters == 0)
+ return Enumerable.Empty();
+
+ var typeParams = new TypeTypeParameter[ThisTypeParameters];
+ var genericParams = td.GetGenericParameters();
+ int toSkip = genericParams.Count - typeParams.Length;
+
+ // Two-phase population because type parameters can be mutually dependent
+ for (int i = 0; i < typeParams.Length; ++i)
+ typeParams[i] = cx.Populate(new TypeTypeParameter(this, this, i));
+ for (int i = 0; i < typeParams.Length; ++i)
+ typeParams[i].PopulateHandle(this, genericParams[i + toSkip]);
+ return typeParams;
+ }
+
+ readonly Lazy> typeParams;
+
+ public override IEnumerable MethodParameters => Enumerable.Empty();
+
+ public override IEnumerable TypeParameters
+ {
+ get
+ {
+ if (declType != null)
+ {
+ foreach (var t in declType.TypeParameters)
+ yield return t;
+ }
+
+ foreach (var t in typeParams.Value)
+ yield return t;
+ }
+ }
+
+ public override IEnumerable Contents
+ {
+ get
+ {
+ foreach (var c in base.Contents) yield return c;
+
+ MakeTypeParameters();
+
+ foreach (var f in td.GetFields())
+ {
+ // Populate field if needed
+ yield return cx.CreateGeneric(this, f);
+ }
+
+ foreach (var prop in td.GetProperties())
+ {
+ yield return new Property(this, this, prop);
+ }
+
+ foreach (var @event in td.GetEvents())
+ {
+ yield return new Event(cx, this, @event);
+ }
+
+ foreach (var a in Attribute.Populate(cx, this, td.GetCustomAttributes()))
+ yield return a;
+
+ foreach (var impl in td.GetMethodImplementations().Select(i => cx.mdReader.GetMethodImplementation(i)))
+ {
+ var m = (Method)cx.CreateGeneric(this, impl.MethodBody);
+ var decl = (Method)cx.CreateGeneric(this, impl.MethodDeclaration);
+
+ yield return m;
+ yield return decl;
+ yield return Tuples.cil_implements(m, decl);
+ }
+
+ if (td.Attributes.HasFlag(TypeAttributes.Abstract))
+ yield return Tuples.cil_abstract(this);
+
+ if (td.Attributes.HasFlag(TypeAttributes.Class))
+ yield return Tuples.cil_class(this);
+
+ if (td.Attributes.HasFlag(TypeAttributes.Interface))
+ yield return Tuples.cil_interface(this);
+
+ if (td.Attributes.HasFlag(TypeAttributes.Public))
+ yield return Tuples.cil_public(this);
+
+ if (td.Attributes.HasFlag(TypeAttributes.Sealed))
+ yield return Tuples.cil_sealed(this);
+
+ if (td.Attributes.HasFlag(TypeAttributes.HasSecurity))
+ yield return Tuples.cil_security(this);
+
+ // Base types
+
+ if (!td.BaseType.IsNil)
+ {
+ var @base = (Type)cx.CreateGeneric(this, td.BaseType);
+ yield return @base;
+ yield return Tuples.cil_base_class(this, @base);
+ }
+
+ foreach (var @interface in td.GetInterfaceImplementations().Select(i => cx.mdReader.GetInterfaceImplementation(i)))
+ {
+ var t = (Type)cx.CreateGeneric(this, @interface.Interface);
+ yield return t;
+ yield return Tuples.cil_base_interface(this, t);
+ }
+
+ // Only type definitions have locations.
+ yield return Tuples.cil_type_location(this, cx.assembly);
+ }
+ }
+
+ internal override Method LookupMethod(StringHandle name, BlobHandle signature)
+ {
+ foreach (var h in td.GetMethods())
+ {
+ var md = cx.mdReader.GetMethodDefinition(h);
+
+ if (md.Name == name && md.Signature == signature)
+ {
+ return (Method)cx.Create(h);
+ }
+ }
+
+ throw new InternalError("Couldn't locate method in type");
+ }
+ }
+
+ ///
+ /// A type reference, to a type in a referenced assembly.
+ ///
+ public sealed class TypeReferenceType : Type
+ {
+ readonly TypeReference tr;
+ readonly Lazy typeParams;
+
+ public TypeReferenceType(Context cx, TypeReferenceHandle handle) : this(cx, cx.mdReader.GetTypeReference(handle))
+ {
+ ShortId = MakeId(false);
+ typeParams = new Lazy(MakeTypeParameters);
+ }
+
+ public TypeReferenceType(Context cx, TypeReference tr) : base(cx)
+ {
+ this.tr = tr;
+ }
+
+ TypeTypeParameter[] MakeTypeParameters()
+ {
+ var typeParams = new TypeTypeParameter[ThisTypeParameters];
+ for (int i = 0; i < typeParams.Length; ++i)
+ {
+ typeParams[i] = new TypeTypeParameter(this, this, i);
+ }
+ return typeParams;
+ }
+
+ public override IEnumerable Contents
+ {
+ get
+ {
+ foreach (var tp in typeParams.Value)
+ yield return tp;
+
+ foreach (var c in base.Contents)
+ yield return c;
+ }
+ }
+
+ public override Id Name
+ {
+ get
+ {
+ var name = cx.GetId(tr.Name);
+ var tick = name.Value.IndexOf('`');
+ return tick == -1 ? name : cx.GetId(name.Value.Substring(0, tick));
+ }
+ }
+
+ public override Namespace Namespace => cx.CreateNamespace(tr.Namespace);
+
+ public override int ThisTypeParameters
+ {
+ get
+ {
+ // Parse the name
+ var name = cx.GetId(tr.Name);
+ var tick = name.Value.IndexOf('`');
+ return tick == -1 ? 0 : int.Parse(name.Value.Substring(tick + 1));
+ }
+ }
+
+ public override IEnumerable ThisGenericArguments
+ {
+ get
+ {
+ foreach (var t in typeParams.Value)
+ yield return t;
+ }
+ }
+
+ public override Type ContainingType
+ {
+ get
+ {
+ if (tr.ResolutionScope.Kind == HandleKind.TypeReference)
+ return (Type)cx.Create((TypeReferenceHandle)tr.ResolutionScope);
+ return null;
+ }
+ }
+
+ public override CilTypeKind Kind => CilTypeKind.ValueOrRefType;
+
+ public override Id AssemblyPrefix
+ {
+ get
+ {
+ switch (tr.ResolutionScope.Kind)
+ {
+ case HandleKind.TypeReference:
+ return ContainingType.AssemblyPrefix;
+ case HandleKind.AssemblyReference:
+ var assemblyDef = cx.mdReader.GetAssemblyReference((AssemblyReferenceHandle)tr.ResolutionScope);
+ return cx.GetId(assemblyDef.Name) + "_" + cx.GetId(assemblyDef.Version.ToString()) + "::";
+ default:
+ return cx.AssemblyPrefix;
+ }
+ }
+ }
+
+ public override IEnumerable TypeParameters => typeParams.Value;
+
+ public override IEnumerable MethodParameters => throw new InternalError("This type does not have method parameters");
+
+ public override Id MakeId(bool inContext)
+ {
+ if (IsPrimitiveType) return PrimitiveTypeId;
+
+ var ct = ContainingType;
+ Id l = null;
+ if (ct != null)
+ {
+ l = ContainingType.GetId(inContext);
+ }
+ else
+ {
+ if (tr.ResolutionScope.Kind == HandleKind.AssemblyReference)
+ {
+ l = AssemblyPrefix;
+ }
+
+ if (!Namespace.IsGlobalNamespace)
+ {
+ l += Namespace.ShortId;
+ }
+ }
+
+ return l + cx.Dot + cx.GetId(tr.Name);
+ }
+
+ public override Type Construct(IEnumerable typeArguments)
+ {
+ if (TotalTypeParametersCheck != typeArguments.Count())
+ throw new InternalError("Mismatched type arguments");
+
+ return cx.Populate(new ConstructedType(cx, this, typeArguments));
+ }
+ }
+
+
+ ///
+ /// A constructed type.
+ ///
+ public sealed class ConstructedType : Type
+ {
+ readonly Type unboundGenericType;
+ readonly Type[] thisTypeArguments;
+
+ public override IEnumerable ThisTypeArguments => thisTypeArguments;
+
+ public override IEnumerable ThisGenericArguments => thisTypeArguments.EnumerateNull();
+
+ public override IEnumerable Contents
+ {
+ get
+ {
+ foreach (var c in base.Contents)
+ yield return c;
+
+ int i = 0;
+ foreach (var type in ThisGenericArguments)
+ {
+ yield return type;
+ yield return Tuples.cil_type_argument(this, i++, type);
+ }
+ }
+ }
+
+ public override Type SourceDeclaration => unboundGenericType;
+
+ public ConstructedType(Context cx, Type unboundType, IEnumerable typeArguments) : base(cx)
+ {
+ var suppliedArgs = typeArguments.Count();
+ if (suppliedArgs != unboundType.TotalTypeParametersCheck)
+ throw new InternalError("Unexpected number of type arguments in ConstructedType");
+
+ unboundGenericType = unboundType;
+ var thisParams = unboundType.ThisTypeParameters;
+ var parentParams = suppliedArgs - thisParams;
+
+ if (typeArguments.Count() == thisParams)
+ {
+ containingType = unboundType.ContainingType;
+ thisTypeArguments = typeArguments.ToArray();
+ }
+ else if (thisParams == 0)
+ {
+ containingType = unboundType.ContainingType.Construct(typeArguments);
+ }
+ else
+ {
+ containingType = unboundType.ContainingType.Construct(typeArguments.Take(parentParams));
+ thisTypeArguments = typeArguments.Skip(parentParams).ToArray();
+ }
+
+ ShortId = MakeId(false);
+ }
+
+ readonly Type containingType;
+ public override Type ContainingType => containingType;
+
+ public override Id Name => unboundGenericType.Name;
+
+ public override Namespace Namespace => unboundGenericType.Namespace;
+
+ public override int ThisTypeParameters => thisTypeArguments == null ? 0 : thisTypeArguments.Length;
+
+ public override CilTypeKind Kind => unboundGenericType.Kind;
+
+ public override Type Construct(IEnumerable typeArguments)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override Id MakeId(bool inContext)
+ {
+ Id l;
+ if (ContainingType != null)
+ {
+ l = ContainingType.GetId(inContext) + cx.Dot;
+ }
+ else
+ {
+ l = AssemblyPrefix;
+
+ if (!Namespace.IsGlobalNamespace)
+ {
+ l += Namespace.ShortId + cx.Dot;
+ }
+ }
+ l += unboundGenericType.Name;
+
+ if (thisTypeArguments != null && thisTypeArguments.Any())
+ {
+ l += open;
+ bool first = true;
+ foreach (var t in thisTypeArguments)
+ {
+ if (first) first = false; else l += comma;
+ l += t.ShortId;
+ }
+ l += close;
+ }
+ return l;
+ }
+
+ static readonly StringId open = new StringId("<");
+ static readonly StringId close = new StringId(">");
+ static readonly StringId comma = new StringId(",");
+
+ public override Id AssemblyPrefix => unboundGenericType.AssemblyPrefix;
+
+ public override IEnumerable TypeParameters => GenericArguments;
+
+ public override IEnumerable MethodParameters => throw new NotImplementedException();
+ }
+
+ public sealed class PrimitiveType : Type
+ {
+ readonly PrimitiveTypeCode typeCode;
+ public PrimitiveType(Context cx, PrimitiveTypeCode tc) : base(cx)
+ {
+ typeCode = tc;
+ ShortId = MakeId(false);
+ }
+
+ public override Id MakeId(bool inContext)
+ {
+ return builtin + Name;
+ }
+
+ public override Id Name => typeCode.Id();
+
+ public override Namespace Namespace => cx.SystemNamespace;
+
+ public override Type ContainingType => null;
+
+ public override int ThisTypeParameters => 0;
+
+ public override CilTypeKind Kind => CilTypeKind.ValueOrRefType;
+
+ static readonly Id empty = new StringId("");
+
+ public override Id AssemblyPrefix => empty;
+
+ public override IEnumerable TypeParameters => throw new NotImplementedException();
+
+ public override IEnumerable MethodParameters => throw new NotImplementedException();
+
+ public override Type Construct(IEnumerable typeArguments) => throw new NotImplementedException();
+ }
+
+ ///
+ /// An array type.
+ ///
+ sealed class ArrayType : Type, IArrayType
+ {
+ readonly Type elementType;
+ readonly int rank;
+
+ public ArrayType(Context cx, Type element, ArrayShape shape) : base(cx)
+ {
+ rank = shape.Rank;
+ elementType = element;
+ ShortId = MakeId(false);
+ }
+
+ public ArrayType(Context cx, Type element) : base(cx)
+ {
+ rank = 1;
+ elementType = element;
+ ShortId = MakeId(false);
+ }
+
+ public override Id MakeId(bool inContext) => elementType.GetId(inContext) + openBracket + rank + closeBracket;
+
+ static readonly StringId openBracket = new StringId("[]");
+ static readonly StringId closeBracket = new StringId("[]");
+
+ public override Id Name => elementType.Name + openBracket + closeBracket;
+
+ public override Namespace Namespace => cx.SystemNamespace;
+
+ public override Type ContainingType => null;
+
+ public override int ThisTypeParameters => elementType.ThisTypeParameters;
+
+ public override CilTypeKind Kind => CilTypeKind.Array;
+
+ public override Type Construct(IEnumerable typeArguments) => cx.Populate(new ArrayType(cx, elementType.Construct(typeArguments)));
+
+ public override Type SourceDeclaration => cx.Populate(new ArrayType(cx, elementType.SourceDeclaration));
+
+ public override IEnumerable Contents
+ {
+ get
+ {
+ foreach (var c in base.Contents)
+ yield return c;
+
+ yield return Tuples.cil_array_type(this, elementType, rank);
+ }
+ }
+
+ public override Id AssemblyPrefix
+ {
+ get
+ {
+ return elementType.AssemblyPrefix;
+ }
+ }
+
+ public override IEnumerable GenericArguments => elementType.GenericArguments;
+
+ public override IEnumerable TypeParameters => elementType.TypeParameters;
+
+ public override IEnumerable MethodParameters => throw new NotImplementedException();
+ }
+
+ interface ITypeParameter : IType
+ {
+ }
+
+ abstract class TypeParameter : Type, ITypeParameter
+ {
+ protected readonly GenericContext gc;
+
+ public TypeParameter(GenericContext gc) : base(gc.cx)
+ {
+ this.gc = gc;
+ }
+
+ public override Namespace Namespace => null;
+
+ public override Type ContainingType => null;
+
+ public override int ThisTypeParameters => 0;
+
+ public override CilTypeKind Kind => CilTypeKind.TypeParameter;
+
+ public override Id AssemblyPrefix => throw new NotImplementedException();
+
+ public override Type Construct(IEnumerable typeArguments) => throw new InternalError("Attempt to construct a type parameter");
+
+ public IEnumerable PopulateHandle(GenericContext gc, GenericParameterHandle parameterHandle)
+ {
+ if (!parameterHandle.IsNil)
+ {
+ var tp = cx.mdReader.GetGenericParameter(parameterHandle);
+
+ if (tp.Attributes.HasFlag(GenericParameterAttributes.Contravariant))
+ yield return Tuples.cil_typeparam_contravariant(this);
+ if (tp.Attributes.HasFlag(GenericParameterAttributes.Covariant))
+ yield return Tuples.cil_typeparam_covariant(this);
+ if (tp.Attributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint))
+ yield return Tuples.cil_typeparam_new(this);
+ if (tp.Attributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint))
+ yield return Tuples.cil_typeparam_class(this);
+ if (tp.Attributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint))
+ yield return Tuples.cil_typeparam_struct(this);
+
+ foreach (var constraint in tp.GetConstraints().Select(h => cx.mdReader.GetGenericParameterConstraint(h)))
+ {
+ var t = (Type)cx.CreateGeneric(this.gc, constraint.Type);
+ yield return t;
+ yield return Tuples.cil_typeparam_constraint(this, t);
+ }
+ }
+ }
+ }
+
+ sealed class MethodTypeParameter : TypeParameter
+ {
+ readonly Method method;
+ readonly int index;
+
+ public override Id MakeId(bool inContext) => inContext && method == gc ? Name : method.ShortId + Name;
+
+ static readonly Id excl = new StringId("!");
+
+ public override Id Name => excl + index.ToString();
+
+ public MethodTypeParameter(GenericContext gc, Method m, int index) : base(gc)
+ {
+ method = m;
+ this.index = index;
+ ShortId = MakeId(false);
+ }
+
+ public override TypeContainer Parent => method;
+
+ public override IEnumerable TypeParameters => throw new NotImplementedException();
+
+ public override IEnumerable MethodParameters => throw new NotImplementedException();
+
+ public override IEnumerable Contents
+ {
+ get
+ {
+ yield return Tuples.cil_type(this, Name.Value, Kind, method, SourceDeclaration);
+ yield return Tuples.cil_type_parameter(method, index, this);
+ }
+ }
+ }
+
+
+ sealed class TypeTypeParameter : TypeParameter
+ {
+ readonly Type type;
+ readonly int index;
+
+ public TypeTypeParameter(GenericContext cx, Type t, int i) : base(cx)
+ {
+ index = i;
+ type = t;
+ ShortId = t.ShortId + Name;
+ }
+
+ public override Id MakeId(bool inContext) => type.MakeId(inContext) + Name;
+
+ public override TypeContainer Parent => type ?? gc as TypeContainer;
+
+ static readonly Id excl = new StringId("!");
+ public override Id Name => excl + index.ToString();
+
+ public override IEnumerable TypeParameters => Enumerable.Empty();
+
+ public override IEnumerable MethodParameters => Enumerable.Empty();
+
+ public override IEnumerable Contents
+ {
+ get
+ {
+ yield return Tuples.cil_type(this, Name.Value, Kind, type, SourceDeclaration);
+ yield return Tuples.cil_type_parameter(type, index, this);
+ }
+ }
+ }
+
+ interface IPointerType : IType
+ {
+ }
+
+ sealed class PointerType : Type, IPointerType
+ {
+ readonly Type pointee;
+
+ public PointerType(Context cx, Type pointee) : base(cx)
+ {
+ this.pointee = pointee;
+ ShortId = MakeId(false);
+ }
+
+ public override Id MakeId(bool inContext) => pointee.MakeId(inContext) + star;
+
+ static readonly StringId star = new StringId("*");
+
+ public override Id Name => pointee.Name + star;
+
+ public override Namespace Namespace => pointee.Namespace;
+
+ public override Type ContainingType => pointee.ContainingType;
+
+ public override TypeContainer Parent => pointee.Parent;
+
+ public override int ThisTypeParameters => 0;
+
+ public override CilTypeKind Kind => CilTypeKind.Pointer;
+
+ public override Id AssemblyPrefix => pointee.AssemblyPrefix;
+
+ public override IEnumerable TypeParameters => throw new NotImplementedException();
+
+ public override IEnumerable MethodParameters => throw new NotImplementedException();
+
+ public override Type Construct(IEnumerable typeArguments) => throw new NotImplementedException();
+
+ public override IEnumerable Contents
+ {
+ get
+ {
+ foreach (var c in base.Contents) yield return c;
+ yield return Tuples.cil_pointer_type(this, pointee);
+ }
+ }
+ }
+
+ sealed class ErrorType : Type
+ {
+ public ErrorType(Context cx) : base(cx)
+ {
+ ShortId = MakeId(false);
+ }
+
+ public override Id MakeId(bool inContext) => CIL.Id.Create("