📜 ⬆️ ⬇️

Experience in custom analysis of c # code

For a long time, I shared the problem of testing the code in the finalizer and recently complained about the test crash ( How to test the finalizer code (c #) and How to test the finalizer code (c #). Afterword: the test still fell ).
During the discussion with the commander withkittens , the idea was expressed:
The finalizer (if properly implemented IDisposable pattern) should (should) call Dispose (false). This fact can be tested by static analysis. Accordingly, if Dispose (false) causes the file to be deleted (did you write the test?), Then you can be sure that the finalizer will also cause the file to be deleted, the unit test is redundant.

This idea seemed very sensible to me, besides, sometimes I want to control the source code more custom than the built-in code analysis or resharper gives.
The experience of implementing custom code analysis rules under the cat + "how ozcode helped in the process of researching an external library"


First of all, I expanded the list of things that I would like to look for:

Validation of a specific field type


I decided to start with the simplest: the log object, if it is declared as a field of a certain class, must be static, as advised on the nlog site .
Thus, the first line fits the rule, and the second does not:
private static ILog m_logger1 = LogManager.GetInstance(); private ILog m_logger2 = LogManager.GetInstance(); 

')

Finalizer must call “Dispose (false)”


Indeed, in order not to write a unit test to the finalizer, it is enough to make sure that it calls Dispose (false), which is tested separately.

The implemented service is registered in the service manager.


One of his colleagues pointed out that sometimes a person from one team implements a certain service, tests it, but forgets to register it with the service manager, and then a person from the other team tries to use this service and gets an error that the service was not found.




Thus, I set myself the task of releasing my own rules through some existing static code analysis tools. The first tool that the same withkittens advised me was the standard built-in Code Analysis .

As it turned out, the threshold for entering into writing your own rules is very low. It is enough to read at least the post How to write custom Visual Studio 2010 . The post describes for VS2010, but I implemented on VS2013 and did not meet any special problems. It should be noted that there is at least one mistake in this post, and, more interestingly, I didn’t manage to integrate my rules normally into the studio itself: the rule is shown, it is not shown (it strains me, but not much, because I I'm still going to run the rules as part of the tests from the command line).

So, step-by-step instruction is here , so I will start with the implementation of my rules (source code on github ):

Validation of a specific type of field



It's very simple: the Check (Member member) virtual method is implemented, which gets a member (Google translator translates member as “member”, but I will not risk it), check that this member is a field of field (as Field), check that it is The field is our log (stupidly by type name), and we check that this field is static or not. If not, create a “new Problem” and paste it into the Problems list.

  public override ProblemCollection Check(Member member) { Field field = member as Field; if (field == null) { // This rule only applies to fields. // Return a null ProblemCollection so no violations are reported for this member. return null; } string actualType = field.Type.FullName; if (actualType == "SamplesForCodeAnalysis.ILog") { if (!field.IsStatic) { Resolution resolution = GetResolution(field, actualType); Problem problem = new Problem(resolution); Problems.Add(problem); } } return Problems; } 


Finalizer must call “Dispose (false)”



This task is more interesting because it requires you to test the whole method. I implemented it very partially, cleanly, to try this way:

We start as usual: the Check (Member member) virtual method is implemented, which gets the member, we check that this member is the method (as Method), we check that it is the finalizer (stupid at the end of the method to “.Finalize”). Next, take the list of instructions of this method “method.Instruction” and look for whether there is at least one instruction that ends with “.Dispose (System.Boolean)”. This is certainly not exactly what is needed, but at least the presence of the fact that there is no call to Dispose will catch this rule.

  public override ProblemCollection Check(Member member) { var method = member as Method; if (method == null) { // This rule only applies to fields. // Return a null ProblemCollection so no violations are reported for this member. return null; } if (method.FullName.EndsWith(".Finalize")) { bool disposeFound = method.Instructions.Any(p => p.Value is Method && (p.Value as Method).FullName.EndsWith(".Dispose(System.Boolean)")); if (!disposeFound) { Resolution resolution = GetResolution(method); var problem = new Problem(resolution); Problems.Add(problem); } return Problems; } return Problems; } 


In the example, I implemented this finalizer:
  ~Samples() { Dispose(true); int i = 0; i++; int k = 56; Dispose(false); } 

and received such a list of instructions and until I understood how to check that there is only one call to Dispose, and moreover with the False parameter:

So there is still something to work on, but it is already possible to catch the absence of the Dispose () call.

The implemented service is registered in the service manager.


Here the task is even more interesting: there is a class-service (this is such a class that inherits from a special interface):
  public class UserService1 : IBaseService { } public class UserService2 : IBaseService { } 

These services must be registered with the manager:
  public class ServiceManager { public IDictionary<string, IBaseService> AllServices = new Dictionary<string, IBaseService>(); public void RegisterAllServices() { AllServices.Add("service1", new UserService1()); } } 

How to catch a situation that the UserService2 service was not registered.
We implement such a rule (taking into account the fact that in this case, the services and the manager are in the same project, if at different times - have not tried):

We implement a slightly different virtual method “Check (TypeNode typeNode)”, check first of all that the type is inherited from the necessary interface (in our case from “.IBaseService”), then look for the service manager through “DeclaringModule.Types” named “.ServiceManager” . In the type found, look for the desired method ".RegisterAllServices" and finally find (or not find) the original type that the service "(.Contains (typeNode.FullName +" ("))". I agree that it is not quite clean, but again, if there is no registration, the rule will trigger and a warning will be received.

  public override ProblemCollection Check(TypeNode typeNode) { if (typeNode.Interfaces.Any()) { InterfaceNode foundServiceInterface = typeNode.Interfaces.First(i => i.FullName.EndsWith(".IBaseService")); if (foundServiceInterface!=null) { bool foundUsage = false; TypeNode serviceManagerTypeNode = foundServiceInterface.DeclaringModule.Types.First(t => t.FullName.EndsWith(".ServiceManager")); if (serviceManagerTypeNode != null) { Member member = serviceManagerTypeNode.Members.First(t => t.FullName.EndsWith(".RegisterAllServices")); var method = member as Method; if (method != null) { foundUsage = method.Instructions.Any(opcode => opcode.Value != null && opcode.Value.ToString().Contains(typeNode.FullName + "(")); } } if (!foundUsage) { Resolution resolution = GetResolution(typeNode.FullName); var problem = new Problem(resolution); Problems.Add(problem); } } } return Problems; } 


I would like to separately note how I studied the types of the FxCopSdk library. Do not think badly, I did not read the documentation (and did not even look for it).
I used OzCode (extension for Visual Sudio).
For example, instead of dripping in the debugger and looking for how to find a link in this object (or rather, a list of links) to the object I need, I simply started the search “find me how to get to the ServiceManager from this object”:

Ok, then the "type" has a list of interfaces, and the interface has a DeclaringModule, and it has a list of types, one of which is ServiceManager.
If ozcode would still have a generic code for typical search tasks!

I also used the opportunity to reveal , which allowed me to see the entire list in the form in which I wanted:


Sources are uploaded to github.com/constructor-igor/UserDefinedCodeAnalysis
In the Samples folder, I put the sample.cmd file, which starts the analysis of these rules using an example (of course, you need to compile everything first).
After running this file, the report file results.xml is generated, which contains approximately the following results:
  <Type Name="LogStaticFieldSamples" Kind="Class" Accessibility="Public" ExternallyVisible="True"> <Members> <Member Name="#m_logger2" Kind="Field" Static="False" Accessibility="Private" ExternallyVisible="False"> <Messages> <Message TypeName="EnforceStaticLogger" Category="MyRules" CheckId="CR1001" Status="Active" Created="2014-06-25 09:11:55Z" FixCategory="NonBreaking"> <Issue Certainty="101" Level="Warning">Field 'LogStaticFieldSamples.m_logger2' recommended be static, because type SamplesForCodeAnalysis.ILog.</Issue> </Message> </Messages> </Member> </Members> </Type> 

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


All Articles