📜 ⬆️ ⬇️

Exception Analyzer based on Roslyn

I have long wanted to deal with analyzers based on Roslin. Moreover, I already had the experience of 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 using Roslyn analyzers, but I decided to start with something simpler.

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


')

Beginning of work


To begin analyzer development, you need to install VS2015 CTP (the easiest way is to take the finished virtual machine ), then you need to install VS 2015 SDK , .NET Compiler Platform SDK Templates and .NET Compiler Platform Syntax Visualizer . The visualizer will be indispensable for understanding how syntax trees look, how to analyze them correctly and how to generate new trees in fixes. Most importantly, you need to install the correct versions of the tools (for CTP6, all VSIX packages must be for CTP6). On the main page of the Roslyn project there will always be the current installation instructions.

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

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



Ways to install analyzers


There are three ways to install the analyzer:

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


· Analyzers can also be distributed via NuGet along with the library, or simply installed via Managed NuGet Packages:


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

Testability

When developing a plug-in for ReSharper, most of all I lacked simple unit tests. The JetBrains team has developed a serious testing infrastructure, but all tests are integration tests. In R #, there are no abstract tests, they all fall into one of the categories: testing the availability of a context action, testing the result of a “fix”, etc. In this case, the test must necessarily slip the cs-file with the code, which will be used to run the analyzer and another file to compare the results. Check your business logic in isolation is impossible!
In Roslin went a simpler way. Analyzers work with syntactic 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); } 


Also very pleased that the tests run quickly, because despite the several million lines of code in the project Roslyn, it is well structured and do not have to load dozens of extra assemblies.

Opportunities

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



Each of these is easiest to demonstrate with an example. Some examples are shown using animation, which is far from ideal. VS2015 is installed on a virtual machine and image capturing is a bit messy. But the essence will be clear.

1. Empty catch block considered harmful !

The most serious code smell when dealing 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 must be prepared for those who intercept 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 the suppression of exceptions. The catch block may not be empty, but it may still “swallow” exceptions. In this case, it is quite hard to come up with the correct fix, especially when suppressing 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 still, he would have been crooked into the board. Roslin has built-in support for analysis of the execution flow (control flow analysis), on the basis of 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 with throw ex; rather than 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 there will be an impression that the catch block is the source.

image

5. Tracing ex.Message considered harmful

Another typical exception handling error 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 contain nothing useful at all!

This analysis issues a warning if the catch block uses (“observes”) the Message property, but is not interested in the details of the exclusions inside.

image

It was because of such a beautiful model of exception tracing that I had to go to work on the first of January and fill in a hotfix on the prod, which would make it clear what’s going on with the system. Never log just ex.Message ! NEVER!

6. Add catched exception as inner exception

The catch block may throw an exception, but even in this case, information about the original exception may be lost. To avoid this, a new exception must contain the original exception as a nested one.
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 one (of course, for this, the generated exception must have a constructor that takes a pair of parameters - string and Exception ):

image

Instead of conclusion


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

Additional links




A couple of introductory articles in MSDN Magazine about creating analyzers:


ZY If you have any wishes for the exception analyzer, then whistle, I will gladly add them.

Source: https://habr.com/ru/post/253833/


All Articles