【问题标题】:Find out PlatformNotSupportedException during build在构建期间找出 PlatformNotSupportedException
【发布时间】:2019-08-02 06:54:31
【问题描述】:

我正在使用.NET Core 2.2Windows 开发一个项目。明年我将在Linux 上构建和支持它。我正在寻找一种方法来标记错误并在代码中使用 PlatformNotSupportedException 时中断构建。

我看到.NET API analyzer 仍处于预发布阶段,自去年以来没有更新。

【问题讨论】:

  • 要求我们推荐或查找工具或软件库的问题被视为离题,请参阅How to Ask 主题。
  • 与其寻找这样的工具,不如提高您的单元/集成测试套件的测试覆盖率,然后您可以通过自动测试轻松触发异常。
  • @LexLi,测试的好主意。可以在引用的程序集like this 中测试PlatformNotSupportedException 的可能性。
  • 顺便问一下,@peval27,您的意思是代码中的异常还是外部 dll 中的异常?
  • @Alex 我的意思是外部 DLL 中的异常

标签: c# .net-core cross-platform code-analysis


【解决方案1】:

您可以使用Mono.Cecil 来检查是否在程序集中的某处引发了异常。

public static class ExceptionHelper<TException> where TException : Exception
{
    private static readonly string typeName = typeof(TException).FullName;

    public static void ThrowIfDetected(Assembly assembly)
    {
        var definition = AssemblyDefinition.ReadAssembly(assembly.Location);
        var exceptions = CreateExceptions(definition);
        if (exceptions.Any())
            throw new AggregateException(exceptions);
    }

    public static void ThrowIfDetected(params Assembly[] assemblies) =>
        ThrowIfDetected(assemblies as IEnumerable<Assembly>);

    public static void ThrowIfDetected(IEnumerable<Assembly> assemblies)
    {
        var exceptions = CreateExceptions(assemblies);
        if (exceptions.Any())
            throw new AggregateException(exceptions);
    }

    private static IEnumerable<Exception> CreateExceptions(IEnumerable<Assembly> assemblies) =>
        assemblies.Select(assembly => AssemblyDefinition.ReadAssembly(assembly.Location))
                  .SelectMany(definition => CreateExceptions(definition));

    private static IEnumerable<Exception> CreateExceptions(AssemblyDefinition definition)
    {
        var methods =
                definition.Modules
                          .SelectMany(m => m.GetTypes())
                          .SelectMany(t => t.Methods)
                          .Where(m => m.HasBody);
        foreach (var method in methods)
        {
            var instructions = method.Body.Instructions
                .Where(i => i.OpCode.Code == Code.Newobj && // new object is created
                            ((MethodReference)i.Operand).DeclaringType.FullName == typeName && // the object is 'TException'
                            i.Next.OpCode.Code == Code.Throw); // and it's immediately thrown
            foreach (var i in instructions)
            {
                var message = $"{definition.FullName} {method.FullName} offset {i.Offset} throws {typeName}";
                yield return new Exception(message);
            }
        }
    }
}

要获取引用的程序集,请使用这样的扩展方法:

public static class AssemblyExtensions
{
    public static Assembly[] ReflectionOnlyLoadReferencedAssemblies(this Assembly assembly) =>
        assembly.GetReferencedAssemblies()
                .Select(a => Assembly.ReflectionOnlyLoad(a.FullName))
                .ToArray();
}

用法:

使用上述代码创建一个新的控制台应用程序并添加对程序集的引用。试试:

try
{
    ExceptionHelper<PlatformNotSupportedException>.ThrowIfDetected(Assembly.GetEntryAssembly().ReflectionOnlyLoadReferencedAssemblies());
}
catch(AggregateException e)
{
    foreach (var inner in e.InnerExceptions)
        Console.WriteLine($"{inner.Message}\n");
}

它给出:

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Void System.Environment::SetEnvironmentVariable(System.String,System.String) offset 82 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.String System.Environment::InternalGetFolderPath(System.Environment/SpecialFolder,System.Environment/SpecialFolderOption,System.Boolean) offset 75 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Void System.Security.Principal.SecurityIdentifier::.ctor(System.Security.Principal.WellKnownSidType,System.Security.Principal.SecurityIdentifier) offset 49 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Int32 System.Security.Principal.Win32::CreateWellKnownSid(System.Security.Principal.WellKnownSidType,System.Security.Principal.SecurityIdentifier,System.Byte[]&) offset 17 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Boolean System.Security.Principal.Win32::IsEqualDomainSid(System.Security.Principal.SecurityIdentifier,System.Security.Principal.SecurityIdentifier) offset 17 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Int32 System.Security.Principal.Win32::GetWindowsAccountDomainSid(System.Security.Principal.SecurityIdentifier,System.Security.Principal.SecurityIdentifier&) offset 17 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Boolean System.Security.Principal.Win32::IsWellKnownSid(System.Security.Principal.SecurityIdentifier,System.Security.Principal.WellKnownSidType) offset 17 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.IntPtr System.StubHelpers.HStringMarshaler::ConvertToNative(System.String) offset 17 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.IntPtr System.StubHelpers.HStringMarshaler::ConvertToNativeReference(System.String,System.Runtime.InteropServices.WindowsRuntime.HSTRING_HEADER*) offset 17 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.String System.StubHelpers.HStringMarshaler::ConvertToManaged(System.IntPtr) offset 17 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Void System.StubHelpers.SystemTypeMarshaler::ConvertToNative(System.Type,System.StubHelpers.TypeNameNative*) offset 17 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Void System.StubHelpers.SystemTypeMarshaler::ConvertToManaged(System.StubHelpers.TypeNameNative*,System.Type&) offset 17 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Int32 System.StubHelpers.HResultExceptionMarshaler::ConvertToNative(System.Exception) offset 17 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Exception System.StubHelpers.HResultExceptionMarshaler::ConvertToManaged(System.Int32) offset 17 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.IntPtr System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeMarshal::StringToHString(System.String) offset 17 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.String System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeMarshal::PtrToStringHString(System.IntPtr) offset 17 throws System.PlatformNotSupportedException

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Void System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeMarshal::FreeHString(System.IntPtr) offset 17 throws System.PlatformNotSupportedException

现在您可以在单元测试中使用它(例如使用NUnit)来自动检查您引用的程序集是否可以抛出PlatformNotSupportedException

[TestFixture]
public class Tests
{
    [Test]
    public void ReferencedAssembliesDoNOtThrowPlatformNotSupportedException()
    {
        Assert.DoesNotThrow(() => ExceptionHelper<PlatformNotSupportedException>.ThrowIfDetected(yourAssembly.ReflectionOnlyLoadReferencedAssemblies()));
    }
}

如果测试失败,如here 所述,则中断构建。

【讨论】:

  • 但这意味着即使我没有使用引发 PlatformNotSupportedException 的方法,测试也会失败。我可以引用一个抛出 PlatformNotSupportedException 的汇编,我不想使用那个特定的方法。
  • @peval27,你可以使用foreach (var method in methods)中的一段代码来测试单个方法。
  • 好的,我跟着你。这可能是一种可能性,但是您需要注意代码库正在使用的任何新方法,这似乎容易出错。还是谢谢
  • @peval27,您可以在引用的程序集中找到方法,然后您可以通过编程方式从程序集中找到对这些方法的调用,例如 here
【解决方案2】:

我建议使用.NET Compiler Platform SDK 创建您自己的规则。您可以找到安装指南here。 您可能会遇到一些安装问题。解决方案是here on stackoverflow。 完成后

  • 在 Visual Studio 中,选择 File > New > Project... Analyzer with code fix (.NET Standard)。将其命名为 PlatformNotSupportedExceptionAnalyzer
  • 在解决方案资源管理器中删除除PlatformNotSupportedExceptionAnalyzer 之外的所有项目。
  • PlatformNotSupportedExceptionAnalyzer 删除.cs.resx 文件。
  • 添加新类Analyzer

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;

// ...

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class Analyzer : DiagnosticAnalyzer
{
    private static readonly string typeName =
        typeof(System.PlatformNotSupportedException).FullName;

    private static readonly DiagnosticDescriptor rule =
        new DiagnosticDescriptor(id: "ThrowsPlatformNotSupportedException",
                                 title: "Throws 'PlatformNotSupportedException'",
                                 messageFormat: "Do not throw 'PlatformNotSupportedException'",
                                 category: "Usage",
                                 defaultSeverity: DiagnosticSeverity.Error,
                                 isEnabledByDefault: true,
                                 description: "Throws 'PlatformNotSupportedException'");

    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
        ImmutableArray.Create(rule);

    public override void Initialize(AnalysisContext context) =>
        context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ThrowStatement);

    private void AnalyzeNode(SyntaxNodeAnalysisContext context)
    {
        if (!(context.Node.ChildNodes().SingleOrDefault() is ObjectCreationExpressionSyntax node))
            return;

        var type = context.SemanticModel.GetTypeInfo(node).Type;
        if ($"{type.ContainingNamespace}.{type.Name}".Equals(typeName))
            context.ReportDiagnostic(Diagnostic.Create(rule, context.Node.GetLocation()));
    }
}

  • 构建解决方案

测试一下:

  • 创建新项目。
  • 转到管理 NuGet 包... -> 设置。
  • 添加新的包源。
  • 将位置设置为您的 PlatformNotSupportedExceptionAnalyzer \bin\Debug 文件夹。
  • 将其命名为 AnalyzerSource 并保存。
  • 将包源设置为AnalyzerSource
  • 在浏览中选择PlatformNotSupportedExceptionAnalyzer 并安装它。

现在试试:

throw new PlatformNotSupportedException();

得到错误:

Error ThrowsPlatformNotSupportedException 不要抛出“PlatformNotSupportedException”

您可以像这样使用#pragma warning disable#pragma warning restore 控制它:

或者像这样使用SuppressMessageAttribute

您也可以在解决方案资源管理器 -> 项目 -> 参考 -> 分析器 -> 您的分析器中更改其严重性

【讨论】:

  • 这假设我正在构建引发 PlatformNotSupportedException 的源代码,而我正在引用标准 .NET 程序集..
  • @peval27,我试图说明这一点。无论如何,解决方案可能会有所帮助。
猜你喜欢
  • 2018-01-11
  • 1970-01-01
  • 1970-01-01
  • 2019-05-26
  • 2018-08-31
  • 2011-09-04
  • 1970-01-01
  • 1970-01-01
  • 2013-04-20
相关资源
最近更新 更多