diff --git a/docs/multitool-usage.md b/docs/multitool-usage.md index 60ed8f30e..d8f85b115 100644 --- a/docs/multitool-usage.md +++ b/docs/multitool-usage.md @@ -66,6 +66,7 @@ Run ```Sarif.Multitool convert --help``` for the current list. - AndroidStudio - ClangAnalyzer +- ClangTidy - CppCheck - ContrastSecurity - Fortify @@ -77,6 +78,15 @@ Run ```Sarif.Multitool convert --help``` for the current list. - StaticDriverVerifier - TSLint +### Observation + +For Clang-tidy you can also provide an extra log file with file name [report file name with extension].log (e.g. report.yml.log) that will ingest and enhance the SARIF file with line number and column number. +Example: +``` +clang-tidy --checks=* --header-filter=.* --system-headers --export-fixes=report.yml &>report.yml.log +``` +This will generate an extra report.yml.log, leave in the same folder with the input report.yml file. + ## Common Arguments | Name | Purpose | diff --git a/src/Sarif.Converters/ClangTidyConverter.cs b/src/Sarif.Converters/ClangTidyConverter.cs index 2b7f0fbdc..9e7334f15 100644 --- a/src/Sarif.Converters/ClangTidyConverter.cs +++ b/src/Sarif.Converters/ClangTidyConverter.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using Microsoft.CodeAnalysis.Sarif.Converters.ClangTidyObjectModel; @@ -15,6 +16,7 @@ namespace Microsoft.CodeAnalysis.Sarif.Converters public class ClangTidyConverter : ToolFileConverterBase { private const string ToolInformationUri = "https://clang.llvm.org/extra/clang-tidy/"; + private static readonly Regex ClangTidyLogRegex = new Regex("(.*):(\\d*):(\\d*): (warning|error): (.*) \\[(.*)\\]", RegexOptions.Compiled); public override string ToolName => "Clang-Tidy"; @@ -25,9 +27,25 @@ public override void Convert(Stream input, IResultLogWriter output, OptionallyEm IDeserializer deserializer = new DeserializerBuilder().Build(); using var textReader = new StreamReader(input); - ClangTidyLog log = deserializer.Deserialize(textReader); + ClangTidyReport report = deserializer.Deserialize(textReader); - (List, List) rulesAndResults = ExtractRulesAndResults(log); + List logs = new List(); + if (report != null) + { + string reportPath = (input as FileStream)?.Name; + if (reportPath != null) + { + string logPath = reportPath + ".log"; + if (File.Exists(logPath)) + { + logs = LoadLogFile(logPath); + } + } + } + + AddLineNumberAndColumnNumber(report, logs); + + (List, List) rulesAndResults = ExtractRulesAndResults(report); var run = new Run { @@ -46,6 +64,46 @@ public override void Convert(Stream input, IResultLogWriter output, OptionallyEm PersistResults(output, rulesAndResults.Item2, run); } + private void AddLineNumberAndColumnNumber(ClangTidyReport report, List logs) + { + if (report.Diagnostics.Count == logs.Count) + { + for (int i = 0; i < logs.Count; i++) + { + report.Diagnostics[i].DiagnosticMessage.LineNumber = logs[i].LineNumber; + report.Diagnostics[i].DiagnosticMessage.ColumnNumber = logs[i].ColumnNumber; + } + } + } + + private List LoadLogFile(string logFilePath) + { + List returnValue = new List(); + + var logLines = File.ReadAllLines(logFilePath).ToList(); + foreach (string line in logLines) + { + Match match = ClangTidyLogRegex.Match(line); + + if (match.Success) + { + int lineNumber; + int columnNumber; + if (int.TryParse(match.Groups[2].Value, out lineNumber) && int.TryParse(match.Groups[3].Value, out columnNumber)) + { + ClangTidyConsoleDiagnostic consoleDiagnostic = new ClangTidyConsoleDiagnostic() + { + LineNumber = lineNumber, + ColumnNumber = columnNumber + }; + returnValue.Add(consoleDiagnostic); + } + } + } + + return returnValue; + } + internal static Result CreateResult(ClangTidyDiagnostic entry) { entry = entry ?? throw new ArgumentNullException(nameof(entry)); @@ -63,6 +121,8 @@ internal static Result CreateResult(ClangTidyDiagnostic entry) Region region = new Region() { CharOffset = entry.DiagnosticMessage.FileOffset, + StartLine = entry.DiagnosticMessage.LineNumber, + StartColumn = entry.DiagnosticMessage.ColumnNumber, }; Uri analysisTargetUri = new Uri(entry.DiagnosticMessage.FilePath, UriKind.RelativeOrAbsolute); @@ -127,12 +187,12 @@ internal static Result CreateResult(ClangTidyDiagnostic entry) return result; } - private static (List, List) ExtractRulesAndResults(ClangTidyLog log) + private static (List, List) ExtractRulesAndResults(ClangTidyReport report) { var rules = new Dictionary(StringComparer.OrdinalIgnoreCase); var results = new List(); - foreach (ClangTidyDiagnostic diagnostic in log.Diagnostics) + foreach (ClangTidyDiagnostic diagnostic in report.Diagnostics) { string ruleId = diagnostic.DiagnosticName; (ReportingDescriptor, Result) ruleAndResult = SarifRuleAndResultFromClangTidyDiagnostic(diagnostic); diff --git a/src/Sarif.Converters/ClangTidyObjectModel/ClangTidyConsoleDiagnostic.cs b/src/Sarif.Converters/ClangTidyObjectModel/ClangTidyConsoleDiagnostic.cs new file mode 100644 index 000000000..75231f1ad --- /dev/null +++ b/src/Sarif.Converters/ClangTidyObjectModel/ClangTidyConsoleDiagnostic.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.CodeAnalysis.Sarif.Converters.ClangTidyObjectModel +{ + public class ClangTidyConsoleDiagnostic + { + public string DiagnosticName { get; set; } + public string Message { get; set; } + public string FilePath { get; set; } + public int ColumnNumber { get; set; } + public int LineNumber { get; set; } + } +} diff --git a/src/Sarif.Converters/ClangTidyObjectModel/ClangTidyDiagnostic.cs b/src/Sarif.Converters/ClangTidyObjectModel/ClangTidyDiagnostic.cs index c667793e7..7e76eb9e8 100644 --- a/src/Sarif.Converters/ClangTidyObjectModel/ClangTidyDiagnostic.cs +++ b/src/Sarif.Converters/ClangTidyObjectModel/ClangTidyDiagnostic.cs @@ -1,11 +1,14 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Collections.Generic; + namespace Microsoft.CodeAnalysis.Sarif.Converters.ClangTidyObjectModel { public class ClangTidyDiagnostic { public string DiagnosticName { get; set; } public ClangTidyDiagnosticMessage DiagnosticMessage { get; set; } + public List Notes { get; set; } } } diff --git a/src/Sarif.Converters/ClangTidyObjectModel/ClangTidyDiagnosticMessage.cs b/src/Sarif.Converters/ClangTidyObjectModel/ClangTidyDiagnosticMessage.cs index e8d09ef84..bdfc18bd1 100644 --- a/src/Sarif.Converters/ClangTidyObjectModel/ClangTidyDiagnosticMessage.cs +++ b/src/Sarif.Converters/ClangTidyObjectModel/ClangTidyDiagnosticMessage.cs @@ -10,6 +10,8 @@ public class ClangTidyDiagnosticMessage public string Message { get; set; } public string FilePath { get; set; } public int FileOffset { get; set; } + public int LineNumber { get; set; } + public int ColumnNumber { get; set; } public List Replacements { get; set; } } } diff --git a/src/Sarif.Converters/ClangTidyObjectModel/ClangTidyLog.cs b/src/Sarif.Converters/ClangTidyObjectModel/ClangTidyReport.cs similarity index 92% rename from src/Sarif.Converters/ClangTidyObjectModel/ClangTidyLog.cs rename to src/Sarif.Converters/ClangTidyObjectModel/ClangTidyReport.cs index b605921bf..c966d62c3 100644 --- a/src/Sarif.Converters/ClangTidyObjectModel/ClangTidyLog.cs +++ b/src/Sarif.Converters/ClangTidyObjectModel/ClangTidyReport.cs @@ -5,7 +5,7 @@ namespace Microsoft.CodeAnalysis.Sarif.Converters.ClangTidyObjectModel { - public class ClangTidyLog + public class ClangTidyReport { public string MainSourceFile { get; set; } public List Diagnostics { get; set; } diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/ClangTidy/ExpectedOutputs/ValidResultsWithLog.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/ClangTidy/ExpectedOutputs/ValidResultsWithLog.sarif new file mode 100644 index 000000000..68f6273c1 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/ClangTidy/ExpectedOutputs/ValidResultsWithLog.sarif @@ -0,0 +1,301 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [ + { + "ruleId": "clang-diagnostic-error", + "message": { + "text": "too many errors emitted, stopping now" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "", + "index": 0 + }, + "region": { + "startLine": 29, + "startColumn": 9, + "charOffset": 0 + } + } + } + ] + }, + { + "ruleId": "readability-convert-member-functions-to-static", + "message": { + "text": "method 'print' can be made static" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/home/lsp/projects/staticlib/src/Hello.cpp", + "index": 1 + }, + "region": { + "startLine": 30, + "startColumn": 9, + "charOffset": 60 + } + } + } + ], + "fixes": [ + { + "artifactChanges": [ + { + "artifactLocation": { + "uri": "/home/lsp/projects/staticlib/src/Hello.cpp", + "index": 1 + }, + "replacements": [ + { + "deletedRegion": { + "charOffset": 67 + }, + "insertedContent": { + "text": "static " + } + } + ] + } + ] + } + ] + }, + { + "ruleId": "clang-diagnostic-error", + "message": { + "text": "non-ASCII characters are not allowed outside of literals and identifiers" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/home/lsp/projects/staticlib/src/README.adoc", + "index": 2 + }, + "region": { + "startLine": 38, + "startColumn": 1, + "charOffset": 385 + } + } + } + ], + "fixes": [ + { + "artifactChanges": [ + { + "artifactLocation": { + "uri": "/home/lsp/projects/staticlib/src/README.adoc", + "index": 2 + }, + "replacements": [ + { + "deletedRegion": { + "charOffset": 385, + "charLength": 3 + } + } + ] + } + ] + } + ] + }, + { + "ruleId": "modernize-use-trailing-return-type", + "message": { + "text": "use a trailing return type for this function" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/home/lsp/projects/staticlib/src/main.cpp", + "index": 3 + }, + "region": { + "startLine": 50, + "startColumn": 7, + "charOffset": 31 + } + } + } + ], + "fixes": [ + { + "artifactChanges": [ + { + "artifactLocation": { + "uri": "/home/lsp/projects/staticlib/src/main.cpp", + "index": 3 + }, + "replacements": [ + { + "deletedRegion": { + "charOffset": 27, + "charLength": 3 + }, + "insertedContent": { + "text": "auto" + } + }, + { + "deletedRegion": { + "charOffset": 59 + }, + "insertedContent": { + "text": " -> int" + } + } + ] + } + ] + } + ] + }, + { + "ruleId": "misc-unused-parameters", + "message": { + "text": "parameter 'argc' is unused" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/home/lsp/projects/staticlib/src/main.cpp", + "index": 3 + }, + "region": { + "startLine": 50, + "startColumn": 15, + "charOffset": 40 + } + } + } + ], + "fixes": [ + { + "artifactChanges": [ + { + "artifactLocation": { + "uri": "/home/lsp/projects/staticlib/src/main.cpp", + "index": 3 + }, + "replacements": [ + { + "deletedRegion": { + "charOffset": 40, + "charLength": 4 + }, + "insertedContent": { + "text": " /*argc*/" + } + } + ] + } + ] + } + ] + }, + { + "ruleId": "misc-unused-parameters", + "message": { + "text": "parameter 'argv' is unused" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/home/lsp/projects/staticlib/src/main.cpp", + "index": 3 + }, + "region": { + "startLine": 100, + "startColumn": 15, + "charOffset": 52 + } + } + } + ], + "fixes": [ + { + "artifactChanges": [ + { + "artifactLocation": { + "uri": "/home/lsp/projects/staticlib/src/main.cpp", + "index": 3 + }, + "replacements": [ + { + "deletedRegion": { + "charOffset": 52, + "charLength": 4 + }, + "insertedContent": { + "text": " /*argv*/" + } + } + ] + } + ] + } + ] + } + ], + "tool": { + "driver": { + "name": "Clang-Tidy", + "informationUri": "https://clang.llvm.org/extra/clang-tidy/", + "rules": [ + { + "id": "clang-diagnostic-error" + }, + { + "id": "readability-convert-member-functions-to-static", + "helpUri": "https://clang.llvm.org/extra/clang-tidy/checks/readability-convert-member-functions-to-static.html" + }, + { + "id": "modernize-use-trailing-return-type", + "helpUri": "https://clang.llvm.org/extra/clang-tidy/checks/modernize-use-trailing-return-type.html" + }, + { + "id": "misc-unused-parameters", + "helpUri": "https://clang.llvm.org/extra/clang-tidy/checks/misc-unused-parameters.html" + } + ] + } + }, + "artifacts": [ + { + "location": { + "uri": "" + } + }, + { + "location": { + "uri": "/home/lsp/projects/staticlib/src/Hello.cpp" + } + }, + { + "location": { + "uri": "/home/lsp/projects/staticlib/src/README.adoc" + } + }, + { + "location": { + "uri": "/home/lsp/projects/staticlib/src/main.cpp" + } + } + ], + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/ClangTidy/Inputs/ValidResultsWithLog.yaml b/src/Test.UnitTests.Sarif.Converters/TestData/ClangTidy/Inputs/ValidResultsWithLog.yaml new file mode 100644 index 000000000..7fe44bbfe --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/ClangTidy/Inputs/ValidResultsWithLog.yaml @@ -0,0 +1,64 @@ +--- +MainSourceFile: '/home/lsp/projects/staticlib/src/Hello.cpp' +Diagnostics: + - DiagnosticName: clang-diagnostic-error + DiagnosticMessage: + Message: too many errors emitted, stopping now + FilePath: '' + FileOffset: 0 + Replacements: [] + - DiagnosticName: readability-convert-member-functions-to-static + DiagnosticMessage: + Message: 'method ''print'' can be made static' + FilePath: '/home/lsp/projects/staticlib/src/Hello.cpp' + FileOffset: 60 + Replacements: + - FilePath: '/home/lsp/projects/staticlib/include/static/Hello.h' + Offset: 67 + Length: 0 + ReplacementText: 'static ' + - DiagnosticName: clang-diagnostic-error + DiagnosticMessage: + Message: non-ASCII characters are not allowed outside of literals and identifiers + FilePath: '/home/lsp/projects/staticlib/src/README.adoc' + FileOffset: 385 + Replacements: + - FilePath: '/home/lsp/projects/staticlib/src/README.adoc' + Offset: 385 + Length: 3 + ReplacementText: '' + - DiagnosticName: modernize-use-trailing-return-type + DiagnosticMessage: + Message: use a trailing return type for this function + FilePath: '/home/lsp/projects/staticlib/src/main.cpp' + FileOffset: 31 + Replacements: + - FilePath: '/home/lsp/projects/staticlib/src/main.cpp' + Offset: 27 + Length: 3 + ReplacementText: auto + - FilePath: '/home/lsp/projects/staticlib/src/main.cpp' + Offset: 59 + Length: 0 + ReplacementText: ' -> int' + - DiagnosticName: misc-unused-parameters + DiagnosticMessage: + Message: 'parameter ''argc'' is unused' + FilePath: '/home/lsp/projects/staticlib/src/main.cpp' + FileOffset: 40 + Replacements: + - FilePath: '/home/lsp/projects/staticlib/src/main.cpp' + Offset: 40 + Length: 4 + ReplacementText: ' /*argc*/' + - DiagnosticName: misc-unused-parameters + DiagnosticMessage: + Message: 'parameter ''argv'' is unused' + FilePath: '/home/lsp/projects/staticlib/src/main.cpp' + FileOffset: 52 + Replacements: + - FilePath: '/home/lsp/projects/staticlib/src/main.cpp' + Offset: 52 + Length: 4 + ReplacementText: ' /*argv*/' +... diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/ClangTidy/Inputs/ValidResultsWithLog.yaml.log b/src/Test.UnitTests.Sarif.Converters/TestData/ClangTidy/Inputs/ValidResultsWithLog.yaml.log new file mode 100644 index 000000000..3a240aba2 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/ClangTidy/Inputs/ValidResultsWithLog.yaml.log @@ -0,0 +1,21 @@ +15125 warnings generated. +30203 warnings generated. +/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/array:29:9: warning: header guard does not follow preferred style [llvm-header-guard] +#ifndef _GLIBCXX_ARRAY + ^ +/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/array:30:9: error: macro '_GLIBCXX_ARRAY' used to declare a constant; consider using a 'constexpr' constant [cppcoreguidelines-macro-usage] +#define _GLIBCXX_ARRAY 1 + ^ +/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/array:38:1: warning: #includes are not sorted properly [llvm-include-order] +#include +^ +/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/array:50:7: error: use 'using' instead of 'typedef' [modernize-use-using] + typedef _Tp _Type[_Nm]; + ^ +/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/array:50:15: warning: do not declare C-style arrays, use std::array<> instead [cppcoreguidelines-avoid-c-arrays] + typedef _Tp _Type[_Nm]; + ^ +/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/array:50:15: note: do not declare C-style arrays, use std::array<> instead +/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/array:100:15: warning: do not declare C-style arrays, use std::array<> instead [cppcoreguidelines-avoid-c-arrays] + typedef _Tp _Type[_Nm]; + ^ \ No newline at end of file