Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find out PlatformNotSupportedException during build

I am developing a project for Windows using .NET Core 2.2. I'm going to build and support it on Linux next year. I am looking for a way to flag up an error and break the build if PlatformNotSupportedException is used in code.

I've seen the .NET API analyzer which is still in pre-release phase and not updated since last year.

like image 671
peval27 Avatar asked Aug 02 '19 06:08

peval27


1 Answers

You can use Mono.Cecil to check if exception is thrown somewhere in an assembly.

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);
            }
        }
    }
}

To get referenced assemblies use such an extension method:

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

Usage:

Create a new console application and add a reference to an assembly with the above code. Try:

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

It gives:

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

Now you can use it in unit testing (e.g. with NUnit) to check automatically if your referenced assemblies can throw PlatformNotSupportedException

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

and break the build if testing fails like described here.

like image 168
Alex Avatar answered Oct 12 '22 23:10

Alex