在MSBuild中解析“静态分析结果交换格式(SARIF)”

时间:2016-03-16 20:07:16

标签: c# msbuild static-code-analysis

使用MSBuild对项目运行各种分析器时,所有故障都将以“静态分析结果交换格式(SARIF)”格式输出(参见例如https://github.com/sarif-standard/sarif-spec)。例如,构建可能会产生以下内容

{
  "version": "0.1",
  "toolInfo": {
    "toolName": "Microsoft (R) Visual C# Compiler",
    "productVersion": "1.1.0",
    "fileVersion": "1.1.0"
  },
  "issues": [
    {
      "ruleId": "SA1401",
      "locations": [
        {
          "analysisTarget": [
            {
              "uri": "C:\\SomeFile.cs",
              "region": {
                "startLine": 708,
                "startColumn": 30,
                "endLine": 708,
                "endColumn": 36
              }
            }
          ]
        }
      ],
      "shortMessage": "Field must be private",
      "fullMessage": "A field within a C# class has an access modifier other than private.",
      "properties": {
        "severity": "Warning",
        "warningLevel": "1",
        "defaultSeverity": "Warning",
        "title": "Fields must be private",
        "category": "StyleCop.CSharp.MaintainabilityRules",
        "helpLink": "https:\/\/github.com\/DotNetAnalyzers\/StyleCopAnalyzers\/blob\/master\/documentation\/SA1401.md",
        "isEnabledByDefault": "True",
        "isSuppressedInSource": "True"
      }
    }
  ]
}

现在我希望能够以最简单的方式解析上面的数据(如果遇到任何非抑制问题,则打破构建)。如何做到这一点?

PS。我最好还是避免实现自己的MSBuild任务和安装特定的软件(例如PowerShell 3.0 - ConvertFrom-Json)。

2 个答案:

答案 0 :(得分:4)

有一个SARIF SDK可用于处理SARIF文件。它可以作为NuGet包Sarif.Sdk使用,源代码在Microsoft / sarif-sdk项目的GitHub上。有一个方法文档docs / how-to.md,它显示了如何从磁盘读取SARIF文件并将其反序列化为SarifLog对象;然后,您可以浏览SARIF对象模型以检查单个结果。

在您的情况下,您对结果的“属性包”中的isSuppressedInSource属性感兴趣。“操作方法”文档说明了如何检索该属性:

Result result = …;

string isSuppressedInSource = result.GetProperty("isSuppressedInSource");

SARIF规范为available online,并且SARIF home page包含指向更多信息的链接。

最后:请注意,Visual Studio 2015 Update 2和Update 3之间的SARIF格式发生了显着变化。格式现在处于稳定的1.0.0版本。

(注意:很抱歉没有提供指向SDK,NuGet软件包和操作方法的直接链接。我没有足够的信誉点来发布两个以上的链接。)

答案 1 :(得分:0)

由于显然没有内置的方法,我最终使用内联的msbuild任务(https://msdn.microsoft.com/en-US/library/dd722601.aspx),定义为

<UsingTask TaskName="ParseUnsupressedAnalysisIssues" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
    <ParameterGroup>
        <Files ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
        <Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
    </ParameterGroup>
    <Task>
        <Reference Include="System.Runtime.Serialization" />
        <Reference Include="System.Xml" />
        <Reference Include="System.Xml.Linq" />
        <Using Namespace="System"/>
        <Using Namespace="System.Collections.Generic"/>
        <Using Namespace="System.IO"/>
        <Using Namespace="System.Linq"/>
        <Using Namespace="System.Runtime.Serialization.Json"/>
        <Using Namespace="System.Xml"/>
        <Using Namespace="System.Xml.Linq"/>
        <Code Type="Fragment" Language="cs">
            <![CDATA[
            List<TaskItem> taskItems = new List<TaskItem>();
            foreach(ITaskItem item in Files)
            {
                try
                {
                    string path = item.GetMetadata("FullPath");
                    using (FileStream fs = new FileStream(path, FileMode.Open))
                    using (XmlDictionaryReader reader = JsonReaderWriterFactory.CreateJsonReader(fs, XmlDictionaryReaderQuotas.Max))
                    {
                        XElement doc = XElement.Load(reader);
                        XElement issuesRoot = doc.Elements("issues").SingleOrDefault();
                        List<XElement> unsupressedIssues = issuesRoot.Elements("item").Where(e => !"True".Equals((string)e.Element("properties").Element("isSuppressedInSource"), StringComparison.Ordinal)).ToList();
                        string unsupressedIssuesString = string.Join(Environment.NewLine, unsupressedIssues);
                        if(!string.IsNullOrEmpty(unsupressedIssuesString))
                        {
                            taskItems.Add(new TaskItem(item.ItemSpec));
                            Console.WriteLine(unsupressedIssuesString);
                        }
                    }
                }
                catch(Exception e)
                {
                    taskItems.Add(new TaskItem(item.ItemSpec));
                    Console.WriteLine(e.ToString());
                }
            }

            Result = taskItems.ToArray();
            ]]>
        </Code>
    </Task>
</UsingTask>

然后可以作为

调用
<ParseUnsupressedAnalysisIssues Files="@(AnalyzerFiles)">
    <Output ItemName="FailedAnalyzerFiles" TaskParameter="Result" />
</ParseUnsupressedAnalysisIssues>
<Error Text="FxCopAll: Following assemblies had analyzer errors @(FailedAnalyzerFiles)" Condition="'@(FailedAnalyzerFiles->Count())' &gt; 0" Code="2"/>