diff --git a/csharp/autobuilder/Semmle.Autobuild.Shared/DiagnosticClassifier.cs b/csharp/autobuilder/Semmle.Autobuild.Shared/DiagnosticClassifier.cs index 94af39ca959..d6a97dd9722 100644 --- a/csharp/autobuilder/Semmle.Autobuild.Shared/DiagnosticClassifier.cs +++ b/csharp/autobuilder/Semmle.Autobuild.Shared/DiagnosticClassifier.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; using Semmle.Util; @@ -57,6 +59,112 @@ namespace Semmle.Autobuild.Shared public virtual void Fire(DiagnosticClassifier classifier, Match match) { } } + /// + /// A which detects arbitrary build messages from MSBuild, CSC, NETSDK, etc. and + /// reports them, up to a limit, as telemetry-only diagnostics. + /// + public class BuildMessageRule : DiagnosticRule + { + public class Result : IDiagnosticsResult, IEquatable + { + /// + /// A value indicating whether this a warning or an error. + /// + public string Type { get; } + /// + /// The source of the message, such as "MSB" or "NETSDK". + /// + public string Source { get; } + /// + /// The numeric id of the message. + /// + public int Code { get; } + /// + /// The message contents. + /// + public string Message { get; } + + public Result(string type, string source, int code, string message) + { + this.Type = type; + this.Source = source; + this.Code = code; + this.Message = message; + } + + public DiagnosticMessage ToDiagnosticMessage(Autobuilder builder, DiagnosticMessage.TspSeverity? severity = null) where T : AutobuildOptionsShared => new( + builder.Options.Language, + $"{this.Source.ToLower()}-{this.Code}", + $"{this.Source.ToUpper()}{this.Code}", + plaintextMessage: this.Message, + severity: + this.Type.Equals("error") ? + DiagnosticMessage.TspSeverity.Error : + DiagnosticMessage.TspSeverity.Warning, + // the messages we capture here are visible in the build log, so there is no need + // to show them other than in telemetry + visibility: new(telemetry: true) + ); + + public bool Equals(Result? x) + { + return x is not null && + x.Code == this.Code && + x.Type == this.Type && + x.Source == this.Source && + x.Message == this.Message; + } + + public override bool Equals(object? obj) + { + return obj is Result && this.Equals(obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(this.Type, this.Source, this.Code, this.Message); + } + } + + /// + /// The maximum number of diagnostics we should emit for this rule. + /// + private const int maxDiagnostics = 10; + /// + /// The number of diagnostics this rule has emitted so far. + /// + private int diagnostics = 0; + + public BuildMessageRule() : base("(?error|warning) (?[A-Z]+)(?\\d+): (?.*)") { } + + public override void Fire(DiagnosticClassifier classifier, Match match) + { + if (!match.Groups.TryGetValue("type", out var type)) + throw new ArgumentException("Expected regular expression match to contain type"); + if (!match.Groups.TryGetValue("source", out var source)) + throw new ArgumentException("Expected regular expression match to contain source"); + if (!match.Groups.TryGetValue("code", out var codeStr)) + throw new ArgumentException("Expected regular expression match to contain code"); + if (!match.Groups.TryGetValue("message", out var message)) + throw new ArgumentException("Expected regular expression match to contain message"); + if (!int.TryParse(codeStr.Value, out var code)) + throw new ArgumentException("Expected code to be numeric"); + + // check that we have not yet exceeded our limit for emitting diagnostics + if (this.diagnostics < BuildMessageRule.maxDiagnostics) + { + var result = new Result(type.Value, source.Value, code, message.Value); + + // add this result if we don't already have an identical one + if (!classifier.Results.OfType().Any(d => d.Equals(result))) + { + classifier.Results.Add(result); + this.diagnostics++; + } + } + } + } + public class DiagnosticClassifier { private readonly List rules; @@ -66,6 +174,8 @@ namespace Semmle.Autobuild.Shared { this.rules = new List(); this.Results = new List(); + + this.AddRule(new BuildMessageRule()); } /// diff --git a/csharp/ql/integration-tests/all-platforms/diag_dotnet_incompatible/diagnostics.expected b/csharp/ql/integration-tests/all-platforms/diag_dotnet_incompatible/diagnostics.expected index 91a9bbbb267..37174a3bd03 100644 --- a/csharp/ql/integration-tests/all-platforms/diag_dotnet_incompatible/diagnostics.expected +++ b/csharp/ql/integration-tests/all-platforms/diag_dotnet_incompatible/diagnostics.expected @@ -34,3 +34,35 @@ "telemetry": true } } +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "plaintextMessage": "The reference assemblies for .NETFramework,Version=v4.0 were not found. To resolve this, install the Developer Pack (SDK/Targeting Pack) for this framework version or retarget your application. You can download .NET Framework Developer Packs at https://aka.ms/msbuild/developerpacks [/test.csproj]", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/msb-3644", + "name": "MSB3644" + }, + "visibility": { + "telemetry": true + } +} +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "plaintextMessage": "Too many project files specified", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/msbuild-4", + "name": "MSBUILD4" + }, + "visibility": { + "telemetry": true + } +} diff --git a/csharp/ql/integration-tests/all-platforms/diag_missing_project_files/diagnostics.expected b/csharp/ql/integration-tests/all-platforms/diag_missing_project_files/diagnostics.expected index da2b3d93941..5c12599bd35 100644 --- a/csharp/ql/integration-tests/all-platforms/diag_missing_project_files/diagnostics.expected +++ b/csharp/ql/integration-tests/all-platforms/diag_missing_project_files/diagnostics.expected @@ -34,3 +34,35 @@ "telemetry": true } } +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "plaintextMessage": "The project file \"/Example.Test.csproj\" was not found. [/test.sln]", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/msb-3202", + "name": "MSB3202" + }, + "visibility": { + "telemetry": true + } +} +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "plaintextMessage": "The project file \"/Example.csproj\" was not found. [/test.sln]", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/msb-3202", + "name": "MSB3202" + }, + "visibility": { + "telemetry": true + } +} diff --git a/csharp/ql/integration-tests/all-platforms/diag_missing_xamarin_sdk/diagnostics.expected b/csharp/ql/integration-tests/all-platforms/diag_missing_xamarin_sdk/diagnostics.expected index 0becfa08cee..c3c4ef179ee 100644 --- a/csharp/ql/integration-tests/all-platforms/diag_missing_xamarin_sdk/diagnostics.expected +++ b/csharp/ql/integration-tests/all-platforms/diag_missing_xamarin_sdk/diagnostics.expected @@ -52,3 +52,35 @@ "telemetry": true } } +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "plaintextMessage": "The imported project \"/usr/local/share/dotnet/sdk/7.0.102/Xamarin/iOS/Xamarin.iOS.CSharp.targets\" was not found. Confirm that the expression in the Import declaration \"/usr/local/share/dotnet/sdk/7.0.102/Xamarin/iOS/Xamarin.iOS.CSharp.targets\" is correct, and that the file exists on disk. [/test.csproj]", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/msb-4019", + "name": "MSB4019" + }, + "visibility": { + "telemetry": true + } +} +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "plaintextMessage": "Too many project files specified", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/msbuild-4", + "name": "MSBUILD4" + }, + "visibility": { + "telemetry": true + } +} diff --git a/csharp/ql/integration-tests/all-platforms/msbuild/diagnostics.expected b/csharp/ql/integration-tests/all-platforms/msbuild/diagnostics.expected new file mode 100644 index 00000000000..70a2ed8b1aa --- /dev/null +++ b/csharp/ql/integration-tests/all-platforms/msbuild/diagnostics.expected @@ -0,0 +1,16 @@ +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "plaintextMessage": "Too many project files specified", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/msbuild-4", + "name": "MSBUILD4" + }, + "visibility": { + "telemetry": true + } +}