Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Roslyn Cookbook

You're reading from   Roslyn Cookbook Compiler as a Service, Code Analysis, Code Quality and more

Arrow left icon
Product type Paperback
Published in Jul 2017
Publisher Packt
ISBN-13 9781787286832
Length 350 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Manish Vasani Manish Vasani
Author Profile Icon Manish Vasani
Manish Vasani
Arrow right icon
View More author details
Toc

Table of Contents (11) Chapters Close

Preface 1. Writing Diagnostic Analyzers FREE CHAPTER 2. Consuming Diagnostic Analyzers in .NET Projects 3. Writing IDE Code Fixes, Refactorings, and Intellisense Completion Providers 4. Improving Code Maintenance of C# Code Base 5. Catch Security Vulnerabilities and Performance Issues in C# Code 6. Live Unit Testing in Visual Studio Enterprise 7. C# Interactive and Scripting 8. Contribute Simple Functionality to Roslyn C# Compiler Open Source Code 9. Design and Implement a New C# Language Feature 10. Command-Line Tools Based on Roslyn API

Creating a compilation analyzer to analyze whole compilation and report issues

A stateful compilation analyzer registers action callbacks that require compilation-wide analysis of symbols and/or syntax to report issues about declarations or executable code in the compilation. These analyzers generally need to initialize some mutable state at the start of the analysis, which is updated while analyzing the compilation, and the final state is used to report diagnostics.

In this section, we will create an analyzer that performs compilation-wide analysis and reports. Diagnostic secure types must not implement interfaces with insecure methods for the following scenarios:

  • Assume we have an interface, say MyNamespace.ISecureType, which is a well-known secure interface, i.e. it is a marker for all secure types in an assembly.
  • Assume we have a method attribute, say MyNamespace.InsecureMethodAttribute, which marks the method on which the attribute is applied as insecure. An interface which has any member with such an attribute, must be considered insecure.
  • We want to report diagnostics for types implementing the well-known secure interface that also implements any insecure interfaces.

Analyzer performs compilation-wide analysis to detect such violating types and reports diagnostics for them in the compilation end action.

Getting ready

You will need to have created and opened an analyzer project, say CSharpAnalyzers in Visual Studio 2017. Refer to the first recipe in this chapter to create this project.

How to do it...

  1. In Solution Explorer, double click on Resources.resx file in CSharpAnalyzers project to open the resource file in the resource editor.
  2. Replace the existing resource strings for AnalyzerDescription, AnalyzerMessageFormat and AnalyzerTitle with new strings.
  1. Replace the Initialize method implementation with the code from CSharpAnalyzers/CSharpAnalyzers/CSharpAnalyzers/DiagnosticAnalyzer.cs/ method named Initialize.

 

  1. Add a private class CompilationAnalyzer from CSharpAnalyzers/CSharpAnalyzers/CSharpAnalyzers/DiagnosticAnalyzer.cs/ type named CompilationAnalyzer in your analyzer to perform the core method body analysis for a given method.
  2. Click on Ctrl + F5 to start a new Visual Studio instance with the analyzer enabled.
  3. In the new Visual Studio instance, enable full solution analysis for C# projects by following the steps here: https://msdn.microsoft.com/en-us/library/mt709421.aspx
  1. In the new Visual Studio instance, create a new C# class library with the following code:
namespace MyNamespace
{
public class InsecureMethodAttribute : System.Attribute { }

public interface ISecureType { }

public interface IInsecureInterface
{
[InsecureMethodAttribute]
void F();
}

class MyInterfaceImpl1 : IInsecureInterface
{
public void F() {}
}

class MyInterfaceImpl2 : IInsecureInterface, ISecureType
{
public void F() {}
}

class MyInterfaceImpl3 : ISecureType
{
public void F() {}
}
}
  1. Verify the analyzer diagnostic is not reported for MyInterfaceImpl1 and MyInterfaceImpl3, but is reported for MyInterfaceImpl2:
  1. Now, change MyInterfaceImpl2 so that it no longer implements IInsecureInterface and verify that the diagnostic is no longer reported.
class MyInterfaceImpl2 : ISecureType
{
public void F() {}
}

How it works...

Compilation analyzers register compilation actions to analyze symbols and/or syntax nodes in the compilation. You can register either a stateless CompilationAction or a stateful CompilationStartAction with nested actions to analyze symbols and/or syntax nodes within a compilation. Our analyzer registers a CompilationStartAction to perform stateful analysis.

context.RegisterCompilationStartAction(compilationContext =>
{
...
}

Analysis begins with a couple of early bail out checks: we are only interested in analyzing compilations which have source or metadata types by name MyNamespace.ISecureType and MyNamespace.InsecureMethodAttribute.

 // Check if the attribute type marking insecure methods is defined.
var insecureMethodAttributeType = compilationContext.Compilation.GetTypeByMetadataName("MyNamespace.InsecureMethodAttribute");
if (insecureMethodAttributeType == null)
{
return;
}

// Check if the interface type marking secure types is defined.
var secureTypeInterfaceType = compilationContext.Compilation.GetTypeByMetadataName("MyNamespace.ISecureType");
if (secureTypeInterfaceType == null)
{
return;
}

We allocate a new CompilationAnalyzer instance for compilations to be analyzed. A constructor of this type initializes the mutable and immutable state tracked for analysis (explained later).

// Initialize state in the start action.
var analyzer = new CompilationAnalyzer(insecureMethodAttributeType, secureTypeInterfaceType);

We then register a nested symbol action, CompilationAnalyzer.AnalyzeSymbol, on the given compilation start context for the given compilation. We register interest in analyzing type and method symbols within the compilation.

// Register an intermediate non-end action that accesses and modifies the state. compilationContext.RegisterSymbolAction(analyzer.AnalyzeSymbol, SymbolKind.NamedType, SymbolKind.Method);

Finally, we register a nested CompilationEndAction to be executed on the instance of CompilationAnalyzer at the end of the compilation analysis.

// Register an end action to report diagnostics based on the final state. compilationContext.RegisterCompilationEndAction(analyzer.CompilationEndAction);
Nested compilation end actions are always guaranteed to be executed after all the nested non-end actions registered on the same analysis context have finished executing.

Let's now understand the working of the core CompilationAnalyzer type to analyze a specific compilation. This analyzer defines an immutable state for type symbols corresponding to the secure interface and insecure method attribute. It also defines mutable state fields to track the set of types defined in the compilation that implement the secure interface and a set of interfaces defined in the compilation that have methods with an insecure method attribute.

#region Per-Compilation immutable state
private readonly INamedTypeSymbol _insecureMethodAttributeType;
private readonly INamedTypeSymbol _secureTypeInterfaceType;
#endregion

#region Per-Compilation mutable state
/// <summary>
/// List of secure types in the compilation implementing secure interface.
/// </summary>
private List<INamedTypeSymbol> _secureTypes;

/// <summary>
/// Set of insecure interface types in the compilation that have methods with an insecure method attribute.
/// </summary>
private HashSet<INamedTypeSymbol> _interfacesWithInsecureMethods;
#endregion

At the start of the analysis, we initialize the set of secure types and interfaces with insecure methods to be empty.

#region State intialization
public CompilationAnalyzer(INamedTypeSymbol insecureMethodAttributeType, INamedTypeSymbol secureTypeInterfaceType)
{
_insecureMethodAttributeType = insecureMethodAttributeType;
_secureTypeInterfaceType = secureTypeInterfaceType;

_secureTypes = null;
_interfacesWithInsecureMethods = null;
}
#endregion

AnalyzeSymbol is registered as a nested symbol action to analyze all types and methods within the compilation. For every type declaration in the compilation, we check whether it implements the secure interface, and if so, add it to our set of secure types. For every method declaration in the compilation, we check whether its containing type is an interface and the method has the insecure method attribute, and if so, add the containing interface type to our set of interface types with insecure methods.

  #region Intermediate actions
public void AnalyzeSymbol(SymbolAnalysisContext context)
{
switch (context.Symbol.Kind)
{
case SymbolKind.NamedType:
// Check if the symbol implements "_secureTypeInterfaceType".
var namedType = (INamedTypeSymbol)context.Symbol;
if (namedType.AllInterfaces.Contains(_secureTypeInterfaceType))
{
_secureTypes = _secureTypes ?? new List<INamedTypeSymbol>();
_secureTypes.Add(namedType);
}

break;

case SymbolKind.Method:
// Check if this is an interface method with "_insecureMethodAttributeType" attribute.
var method = (IMethodSymbol)context.Symbol;
if (method.ContainingType.TypeKind == TypeKind.Interface && method.GetAttributes().Any(a => a.AttributeClass.Equals(_insecureMethodAttributeType)))
{
_interfacesWithInsecureMethods = _interfacesWithInsecureMethods ?? new HashSet<INamedTypeSymbol>();
_interfacesWithInsecureMethods.Add(method.ContainingType);
}

break;
}
}
#endregion

Finally, the registered the compilation end action uses the final state at the end of compilation analysis to report diagnostics. Analysis in this action starts by bailing out early if we either have no secure types or no interfaces with insecure methods. Then, we walk through all secure types and all interfaces with insecure methods, and for every pair. check whether the secure type or any of its base types implements the insecure interface. If so, we report a diagnostic on the secure type.

   #region End action
public void CompilationEndAction(CompilationAnalysisContext context)
{
if (_interfacesWithInsecureMethods == null || _secureTypes == null)
{
// No violating types.
return;
}

// Report diagnostic for violating named types.
foreach (var secureType in _secureTypes)
{
foreach (var insecureInterface in _interfacesWithInsecureMethods)
{
if (secureType.AllInterfaces.Contains(insecureInterface))
{
var diagnostic = Diagnostic.Create(Rule, secureType.Locations[0], secureType.Name, "MyNamespace.ISecureType", insecureInterface.Name);
context.ReportDiagnostic(diagnostic);

break;
}
}
}
}
#endregion
You have been reading a chapter from
Roslyn Cookbook
Published in: Jul 2017
Publisher: Packt
ISBN-13: 9781787286832
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime