Roslyn-based exception analyzer

    For a long time I wanted to deal with Roslin-based analyzers. Moreover, I already had experience creating plugins for Resharper ( R # Contract Editor Extension ), so I wanted to compare different infrastructures and usability. There is an idea to rewrite this plugin with the help of Roslyn analyzers, but I decided to start with something simpler.

    The goal of the weekly project was this : to make a simple analyzer that will show typical errors of exception handling. The most painful from my point of view are:

    • Re-throwing exceptions with throw ex;
    • “Swallow” all exceptions using empty catch {} or catch (Exception) {} blocks .
    • “Swallowing” exceptions in certain branches of a catch block .
    • Saving only ex.Message messages in the logs , while losing potentially important information about where the exception occurred.
    • Invalid throwing new exceptions from catch block .



    Beginning of work


    To start the development of the analyzer, you need to install the VS2015 CTP (the easiest way is to take a ready-made virtual network ), after that you need to install the VS 2015 SDK , .NET Compiler Platform SDK Templates and .NET Compiler Platform Syntax Visualizer . The visualizer will be indispensable for understanding what syntax trees look like, how to analyze them correctly and how to generate new trees in fixes. Most importantly, you need to install the correct version of the tools (for CTP6, all VSIX packages must be for CTP6). The Roslyn project home page will always have up-to-date installation instructions.

    The development team has done a great job so that the creation of analyzers is as simple and convenient as possible. It is enough to create a new project, select the Extensibility -> Diagnostics with Code Fix (Nuget + VSIX) template, drive in the analyzer name and that’s it. As a result, three projects will be created: the analyzer itself, the project with unit tests and the project with the installer (VSIX). By default, an example analyzer has been added to the project, which displays a warning on type names with lowercase letters.

    After that, you can run the tests, or select the VSIX project as the starting one, and press F5. Then another instance of Visual Studio will be launched with the analyzer installed, which will issue a warning for all types with lowercase letters:



    Analyzer Installation Methods


    There are three ways to install the analyzer:

    · Using the VSIX package, which can be downloaded directly from Visual Studio Gallary , or through “Extensions and Updates”.
    · Manually install an analyzer for each project:


    · Also, analyzers can be distributed via NuGet together with the library, or simply installed via Managed NuGet Packages:


    In this case, the link to the package will be commomited into source control, which will allow all project participants to use one set of analyzers.

    Testability

    When developing a plug-in for ReSharper, I most of all lacked simple unit tests. The JetBrains team has developed a serious testing infrastructure, but all tests are integration tests. There are no abstract tests in R #, they all fall into one of the categories: testing the availability of contextual action, testing the result of a “fix”, etc. In this test, it is necessary to slip the cs-file with the code, according to which the analyzer and another file will be run to compare the results. Testing your business logic in isolation is impossible!
    In Roslyn, they took a simpler path. The analyzers work with syntax and semantic trees (Syntax Tree and Semantic Tree), which are easy to create in tests from a file or from a string. As a result, in the test you can check the analyzer itself, or you can check your business model by passing fragments of the syntax tree to it:

    [TestMethod]
    public void SimpleTestWarningOnEmptyBlockThatCatchesException()
    {
        var test = @"
    using System;
    namespace ConsoleApplication1
    {
        class TypeName
        {
            public static void Foo()
            {
                try { Console.WriteLine(); }
                {on}catch(System.Exception) {}
            }
        }
    }";
        var warningPosition = test.IndexOf("{on}");
        var diagnostic = GetSortedDiagnostics(test.Replace("{on}", "")).Single();
        Assert.AreEqual(EmptyCatchBlockAnalyzer.DiagnosticId, diagnostic.Id);
        Assert.AreEqual("'catch(System.Exception)' block is empty. 
                          Do you really know what the app state is?",
             diagnostic.GetMessage());
        Assert.AreEqual(warningPosition, diagnostic.Location.SourceSpan.Start);
    }
    


    It is also very encouraging that the tests run quickly, because despite several million lines of code in the Roslin project, it is well structured and does not have to load dozens of extra assemblies.

    Opportunities Received

    So what can the resulting analyzer do? At the moment, it supports six basic rules:



    Each of them is easiest to demonstrate using an example. Some examples are shown using animations that are far from ideal. VS2015 is installed on the virtual machine and the image capture is slightly skewed at the same time. But the essence will be clear.

    1. Empty catch block considered harmful !

    The most serious code smell when working with exceptions is the complete suppression of all exceptions with an empty catch or catch (Exception) block :

    image

    2. Swallow exceptions considered harmful A
    special place on the boiler should be prepared for those who catch all exceptions using the catch {} block. The fix in this case is very simple: adding throw;

    image

    3. Catch block swallows an exception

    Another analyzer that warns about suppression of exceptions. The catch block may not be empty, while it may still “swallow” exceptions. In this case, it’s quite difficult to come up with the correct fix, especially when suppression of an exception occurs only in one of the branches of the catch block:

    image

    Yes, an attempt to make such an analyzer without Roslin would lead to months of work and anyway, it would be crooked in the board. Roslin has built-in support for control flow analysis, based on which it was not difficult to make this analyzer.

    4. Rethrow exception properly

    This is one of the most common errors when throwing an exception is done using throw ex; , not using throw . Just in case, let me remind you that in the first case, the stack trace of the original exception will be lost and the impression will be that the catch block is the source.

    image

    5. Tracing ex.Message considered harmful

    Another typical error of exception handling when only ex.Message and ex.StackTrace are saved to the console or log . Since exceptions very often form an exception tree, a top-level message may not contain anything useful at all!

    This analysis generates a warning if the catch block uses (“watches”) the Message property, but not interested in the details of the internals of the exception.

    image

    It was because of such a beautiful model for tracing exceptions that I had to go to work on January 1 and upload a hot fix to the prod that would make it clear what was going on with the system. Never log only ex.Message ! NEVER!

    6. Add catched exception as inner exception

    The catch block may throw an exception, but even then, the information about the original exception may be lost. To avoid this, the new exception should contain the original exception as a nested exception.
    This analyzer checks the code for generating a new exception and if the original exception is not used, it suggests adding it as a nested exception (naturally, for this, the generated exception must have a constructor that takes a couple of parameters - string and Exception ):

    image

    Instead of a conclusion


    I do not want to dwell on the development of the analyzer itself, at least not in this post. There are quite good examples on the net (links at the end of the article), and besides, I do not consider myself an expert in this matter. The point of this post is to show the ease of creating the analyzer with your own hands, since all the black work for you will be done by Roslyn. So if suddenly you have some typical problem with the code in your team and you want to formalize a certain coding standard, then writing your own analyzer would be a good idea.

    Sitelinks




    A couple of introductory articles on MSDN Magazine on creating parsers:


    Z.Y. If you have any wishes for the exception analyzer, then whistle, I will add them with pleasure.

    Also popular now: