Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
fb6c27b
Refactor env var code in Autobuilder class
mbg Feb 10, 2023
fce9cb0
Read `..._DIAGNOSTIC_DIR` variable
mbg Feb 10, 2023
4e7c39a
Add initial code for diagnostic messages
mbg Feb 10, 2023
f4c4871
Add basic reporting of a general autobuild failure
mbg Feb 14, 2023
5963501
BuildCommandAutoRule: expose more information
mbg Feb 14, 2023
7b5e19d
Add diagnostics for `BuildCommandAutoRule`
mbg Feb 14, 2023
219b232
Add no projects/solutions diagnostic
mbg Feb 14, 2023
c5a2cfc
Fixup: We => CodeQL
mbg Feb 15, 2023
9992491
Support asynchronous stdout/stderr processing
mbg Feb 15, 2023
9865c50
Change logic for autobuild failures
mbg Feb 15, 2023
61ff4c7
Set `DiagnosticMessage` defaults
mbg Feb 15, 2023
f68c529
Report projects incompatible with .NET Core
mbg Feb 15, 2023
eda33fc
Track which projects/solutions fail to build
mbg Feb 15, 2023
c55281a
Report .NET Core & MSBuild failures
mbg Feb 15, 2023
7e48084
Fixup: better error message for `no-projects-or-solutions`
mbg Feb 15, 2023
5537d79
Detect missing Xamarin SDKs
mbg Feb 16, 2023
aa6efce
Use `TryGetValue`
mbg Feb 16, 2023
8e83fd0
Update C/C++ autobuilder
mbg Feb 16, 2023
0f32099
Make improvements based on PR feedback
mbg Feb 22, 2023
08b51c3
Link to docs for autobuild failures
mbg Feb 21, 2023
c3e25d2
Add docs link for missing Xamarin SDKs
mbg Feb 21, 2023
93b7a2b
Fix: drop please
mbg Feb 21, 2023
b034b2f
Refactor autobuild logic into an `IBuildRule`
mbg Feb 23, 2023
3bf6b6f
Add helper for markdown lists of projects
mbg Feb 23, 2023
1e2329d
Add diagnostic for missing project files
mbg Feb 22, 2023
bdbcaab
Use relative paths
mbg Feb 24, 2023
7de2655
Add tests for build script diagnostics
mbg Feb 24, 2023
b203533
Fix C++ test missing env var
mbg Feb 24, 2023
430af66
Show .NET core error only if files exist
mbg Feb 24, 2023
354f716
Add test for dotnet incompatible projects
mbg Feb 24, 2023
3ef3441
Add test for missing project files
mbg Feb 24, 2023
3167343
Add test for missing Xamarin SDKs
mbg Feb 24, 2023
e2af8f1
Simplify Xamarin query to be platform-independent
mbg Feb 24, 2023
e60676f
Fix `IDisposable` contract violation
mbg Feb 28, 2023
e3762c7
Move `Language` class to `Semmle.Util`
mbg Feb 28, 2023
fea29d5
Refactor to avoid public setters
mbg Feb 28, 2023
f22c864
Fix expected test output for Windows tests
mbg Feb 28, 2023
4903924
Apply ql-for-ql suggestion
mbg Feb 28, 2023
93a45fc
Simplify DiagnosticClassifier in CSharpAutobuilder
mbg Mar 1, 2023
2525ac3
C#: Use dependency injection in the auto builder for Diagnostic class…
michaelnebel Mar 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions cpp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Xunit;
using Semmle.Autobuild.Shared;
using Semmle.Util;
using System.Collections.Generic;
using System;
using System.Linq;
Expand Down Expand Up @@ -75,6 +76,15 @@ int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory,
throw new ArgumentException("Missing RunProcess " + pattern);
}

int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory, IDictionary<string, string>? env, BuildOutputHandler onOutput, BuildOutputHandler onError)
{
var ret = (this as IBuildActions).RunProcess(cmd, args, workingDirectory, env, out var stdout);

stdout.ForEach(line => onOutput(line));

return ret;
}

public IList<string> DirectoryDeleteIn = new List<string>();

void IBuildActions.DirectoryDelete(string dir, bool recursive)
Expand Down Expand Up @@ -243,6 +253,7 @@ CppAutobuilder CreateAutoBuilder(bool isWindows,
Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_TRAP_DIR"] = "";
Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_SOURCE_ARCHIVE_DIR"] = "";
Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_ROOT"] = $@"C:\codeql\{codeqlUpperLanguage.ToLowerInvariant()}";
Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_DIAGNOSTIC_DIR"] = Path.GetTempPath();
Actions.GetEnvironmentVariable["CODEQL_JAVA_HOME"] = @"C:\codeql\tools\java";
Actions.GetEnvironmentVariable["CODEQL_PLATFORM"] = "win64";
Actions.GetEnvironmentVariable["SEMMLE_DIST"] = @"C:\odasa";
Expand Down
3 changes: 2 additions & 1 deletion cpp/autobuilder/Semmle.Autobuild.Cpp/CppAutobuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Semmle.Autobuild.Shared;
using Semmle.Util;

namespace Semmle.Autobuild.Cpp
{
Expand All @@ -21,7 +22,7 @@ public CppAutobuildOptions(IBuildActions actions) : base(actions)

public class CppAutobuilder : Autobuilder<CppAutobuildOptions>
{
public CppAutobuilder(IBuildActions actions, CppAutobuildOptions options) : base(actions, options) { }
public CppAutobuilder(IBuildActions actions, CppAutobuildOptions options) : base(actions, options, new DiagnosticClassifier()) { }

public override BuildScript GetBuildScript()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Xunit;
using Semmle.Autobuild.Shared;
using Semmle.Util;
using System.Collections.Generic;
using System;
using System.Linq;
Expand Down Expand Up @@ -85,6 +86,15 @@ int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory,
return ret;
}

int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory, IDictionary<string, string>? env, BuildOutputHandler onOutput, BuildOutputHandler onError)
{
var ret = (this as IBuildActions).RunProcess(cmd, args, workingDirectory, env, out var stdout);

stdout.ForEach(line => onOutput(line));

return ret;
}

public IList<string> DirectoryDeleteIn { get; } = new List<string>();

void IBuildActions.DirectoryDelete(string dir, bool recursive)
Expand Down Expand Up @@ -391,6 +401,7 @@ private CSharpAutobuilder CreateAutoBuilder(bool isWindows,
actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_TRAP_DIR"] = "";
actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_SOURCE_ARCHIVE_DIR"] = "";
actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_ROOT"] = $@"C:\codeql\{codeqlUpperLanguage.ToLowerInvariant()}";
actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_DIAGNOSTIC_DIR"] = Path.GetTempPath();
actions.GetEnvironmentVariable["CODEQL_JAVA_HOME"] = @"C:\codeql\tools\java";
actions.GetEnvironmentVariable["CODEQL_PLATFORM"] = isWindows ? "win64" : "linux64";
actions.GetEnvironmentVariable["LGTM_INDEX_VSTOOLS_VERSION"] = vsToolsVersion;
Expand Down
69 changes: 69 additions & 0 deletions csharp/autobuilder/Semmle.Autobuild.CSharp/AutoBuildRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using Semmle.Autobuild.Shared;
using Semmle.Extraction.CSharp;

namespace Semmle.Autobuild.CSharp
{
internal class AutoBuildRule : IBuildRule<CSharpAutobuildOptions>
{
private readonly CSharpAutobuilder autobuilder;

public DotNetRule DotNetRule { get; }

public MsBuildRule MsBuildRule { get; }

public BuildCommandAutoRule BuildCommandAutoRule { get; }

public AutoBuildRule(CSharpAutobuilder autobuilder)
{
this.autobuilder = autobuilder;
this.DotNetRule = new DotNetRule();
this.MsBuildRule = new MsBuildRule();
this.BuildCommandAutoRule = new BuildCommandAutoRule(DotNetRule.WithDotNet);
}

public BuildScript Analyse(IAutobuilder<CSharpAutobuildOptions> builder, bool auto)
{
var cleanTrapFolder =
BuildScript.DeleteDirectory(this.autobuilder.TrapDir);
var cleanSourceArchive =
BuildScript.DeleteDirectory(this.autobuilder.SourceArchiveDir);
var tryCleanExtractorArgsLogs =
BuildScript.Create(actions =>
{
foreach (var file in Extractor.GetCSharpArgsLogs())
{
try
{
actions.FileDelete(file);
}
catch // lgtm[cs/catch-of-all-exceptions] lgtm[cs/empty-catch-block]
{ }
Comment on lines +39 to +40

Check notice

Code scanning / CodeQL

Generic catch clause

Generic catch clause.
Comment on lines +39 to +40

Check notice

Code scanning / CodeQL

Poor error handling: empty catch block

Poor error handling: empty catch block.
}

return 0;
});
var attemptExtractorCleanup =
BuildScript.Try(cleanTrapFolder) &
BuildScript.Try(cleanSourceArchive) &
tryCleanExtractorArgsLogs &
BuildScript.DeleteFile(Extractor.GetCSharpLogPath());

/// <summary>
/// Execute script `s` and check that the C# extractor has been executed.
/// If either fails, attempt to cleanup any artifacts produced by the extractor,
/// and exit with code 1, in order to proceed to the next attempt.
/// </summary>
BuildScript IntermediateAttempt(BuildScript s) =>
(s & this.autobuilder.CheckExtractorRun(false)) |
(attemptExtractorCleanup & BuildScript.Failure);

return
// First try .NET Core
IntermediateAttempt(this.DotNetRule.Analyse(this.autobuilder, true)) |
// Then MSBuild
(() => IntermediateAttempt(this.MsBuildRule.Analyse(this.autobuilder, true))) |
// And finally look for a script that might be a build script
(() => this.BuildCommandAutoRule.Analyse(this.autobuilder, true) & this.autobuilder.CheckExtractorRun(true));
}
}
}
188 changes: 132 additions & 56 deletions csharp/autobuilder/Semmle.Autobuild.CSharp/CSharpAutobuilder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Semmle.Extraction.CSharp;
using Semmle.Util.Logging;
using Semmle.Autobuild.Shared;
using Semmle.Util;
using System.Linq;

namespace Semmle.Autobuild.CSharp
{
Expand Down Expand Up @@ -29,25 +31,16 @@ public CSharpAutobuildOptions(IBuildActions actions) : base(actions)

public class CSharpAutobuilder : Autobuilder<CSharpAutobuildOptions>
{
public CSharpAutobuilder(IBuildActions actions, CSharpAutobuildOptions options) : base(actions, options) { }
private const string buildCommandDocsUrl =
"https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages";

public override BuildScript GetBuildScript()
{
/// <summary>
/// A script that checks that the C# extractor has been executed.
/// </summary>
BuildScript CheckExtractorRun(bool warnOnFailure) =>
BuildScript.Create(actions =>
{
if (actions.FileExists(Extractor.GetCSharpLogPath()))
return 0;
private readonly AutoBuildRule autoBuildRule;

if (warnOnFailure)
Log(Severity.Error, "No C# code detected during build.");

return 1;
});
public CSharpAutobuilder(IBuildActions actions, CSharpAutobuildOptions options) : base(actions, options, new CSharpDiagnosticClassifier()) =>
this.autoBuildRule = new AutoBuildRule(this);

public override BuildScript GetBuildScript()
{
var attempt = BuildScript.Failure;
switch (GetCSharpBuildStrategy())
{
Expand All @@ -65,47 +58,9 @@ BuildScript CheckExtractorRun(bool warnOnFailure) =>
attempt = new DotNetRule().Analyse(this, false) & CheckExtractorRun(true);
break;
case CSharpBuildStrategy.Auto:
var cleanTrapFolder =
BuildScript.DeleteDirectory(TrapDir);
var cleanSourceArchive =
BuildScript.DeleteDirectory(SourceArchiveDir);
var tryCleanExtractorArgsLogs =
BuildScript.Create(actions =>
{
foreach (var file in Extractor.GetCSharpArgsLogs())
{
try
{
actions.FileDelete(file);
}
catch // lgtm[cs/catch-of-all-exceptions] lgtm[cs/empty-catch-block]
{ }
}

return 0;
});
var attemptExtractorCleanup =
BuildScript.Try(cleanTrapFolder) &
BuildScript.Try(cleanSourceArchive) &
tryCleanExtractorArgsLogs &
BuildScript.DeleteFile(Extractor.GetCSharpLogPath());

/// <summary>
/// Execute script `s` and check that the C# extractor has been executed.
/// If either fails, attempt to cleanup any artifacts produced by the extractor,
/// and exit with code 1, in order to proceed to the next attempt.
/// </summary>
BuildScript IntermediateAttempt(BuildScript s) =>
(s & CheckExtractorRun(false)) |
(attemptExtractorCleanup & BuildScript.Failure);

attempt =
// First try .NET Core
IntermediateAttempt(new DotNetRule().Analyse(this, true)) |
// Then MSBuild
(() => IntermediateAttempt(new MsBuildRule().Analyse(this, true))) |
// And finally look for a script that might be a build script
(() => new BuildCommandAutoRule(DotNetRule.WithDotNet).Analyse(this, true) & CheckExtractorRun(true)) |
// Attempt a few different build strategies to see if one works
this.autoBuildRule.Analyse(this, true) |
// All attempts failed: print message
AutobuildFailure();
break;
Expand All @@ -114,6 +69,127 @@ BuildScript IntermediateAttempt(BuildScript s) =>
return attempt;
}

/// <summary>
/// A script that checks that the C# extractor has been executed.
/// </summary>
public 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;
});

protected override void AutobuildFailureDiagnostic()
{
// if `ScriptPath` is not null here, the `BuildCommandAuto` rule was
// run and found at least one script to execute
if (this.autoBuildRule.BuildCommandAutoRule.ScriptPath is not null)
{
var relScriptPath = this.MakeRelative(autoBuildRule.BuildCommandAutoRule.ScriptPath);

// if we found multiple build scripts in the project directory, then we can say
// as much to indicate that we may have picked the wrong one;
// otherwise, we just report that the one script we found didn't work
DiagnosticMessage message =
this.autoBuildRule.BuildCommandAutoRule.CandidatePaths.Count() > 1 ?
new(
this.Options.Language,
"multiple-build-scripts",
"There are multiple potential build scripts",
markdownMessage:
"CodeQL found multiple potential build scripts for your project and " +
$"attempted to run `{relScriptPath}`, which failed. " +
"This may not be the right build script for your project. " +
$"Set up a [manual build command]({buildCommandDocsUrl})."
) :
new(
this.Options.Language,
"script-failure",
"Unable to build project using build script",
markdownMessage:
"CodeQL attempted to build your project using a script located at " +
$"`{relScriptPath}`, which failed. " +
$"Set up a [manual build command]({buildCommandDocsUrl})."
);

AddDiagnostic(message);
}

// project files which don't exist get marked as not .NET core projects, but we don't want
// to show an error for this if the files don't exist
var foundNotDotNetProjects = autoBuildRule.DotNetRule.NotDotNetProjects.Where(
proj => this.Actions.FileExists(proj.FullPath)
);

// both dotnet and msbuild builds require project or solution files; if we haven't found any
// then neither of those rules would've worked
if (this.ProjectsOrSolutionsToBuild.Count == 0)
{
this.AddDiagnostic(new(
this.Options.Language,
"no-projects-or-solutions",
"No project or solutions files found",
markdownMessage:
"CodeQL could not find any project or solution files in your repository. " +
$"Set up a [manual build command]({buildCommandDocsUrl})."
));
}
// show a warning if there are projects which are not compatible with .NET Core, in case that is unintentional
else if (foundNotDotNetProjects.Any())
{
this.AddDiagnostic(new(
this.Options.Language,
"dotnet-incompatible-projects",
"Some projects are incompatible with .NET Core",
severity: DiagnosticMessage.TspSeverity.Warning,
markdownMessage: $"""
CodeQL found some projects which cannot be built with .NET Core:

{autoBuildRule.DotNetRule.NotDotNetProjects.Select(p => this.MakeRelative(p.FullPath)).ToMarkdownList(MarkdownUtil.CodeFormatter, 5)}
"""
));
}

// report any projects that failed to build with .NET Core
if (autoBuildRule.DotNetRule.FailedProjectsOrSolutions.Any())
{
this.AddDiagnostic(new(
this.Options.Language,
"dotnet-build-failure",
"Some projects or solutions failed to build using .NET Core",
markdownMessage: $"""
CodeQL was unable to build the following projects using .NET Core:

{autoBuildRule.DotNetRule.FailedProjectsOrSolutions.Select(p => this.MakeRelative(p.FullPath)).ToMarkdownList(MarkdownUtil.CodeFormatter, 10)}

Set up a [manual build command]({buildCommandDocsUrl}).
"""
));
}

// report any projects that failed to build with MSBuild
if (autoBuildRule.MsBuildRule.FailedProjectsOrSolutions.Any())
{
this.AddDiagnostic(new(
this.Options.Language,
"msbuild-build-failure",
"Some projects or solutions failed to build using MSBuild",
markdownMessage: $"""
CodeQL was unable to build the following projects using MSBuild:

{autoBuildRule.MsBuildRule.FailedProjectsOrSolutions.Select(p => this.MakeRelative(p.FullPath)).ToMarkdownList(MarkdownUtil.CodeFormatter, 10)}

Set up a [manual build command]({buildCommandDocsUrl}).
"""
));
}
}

/// <summary>
/// Gets the build strategy that the autobuilder should apply, based on the
/// options in the `lgtm.yml` file.
Expand Down
Loading