C# Code Analysis Using Roslyn Semantic Model

watch_later 13 April, 2021

 Introduction

In the previous notes, we analyzed the source files, relying entirely on the syntax tree and traversing its nodes. In many cases, traversing a single syntax tree is not enough to perform deep analysis. Here the concept ''semantic model'' comes in handy. A semantic model opens up new possibilities for analysis. Great responsibility comes with great power. Therefore, you should know about some features of using a semantic model. This article will cover these points.

Roslyn Semantic Model

Semantic model and symbols

A semantic model provides information about various types of entities, such as methods, local variables, fields, properties, and so on. To get a correct semantic model, we need to compile the project. We'll need an object of the Compilation type for further work. One of the ways to get a compilation object is to call the GetCompilationAsync method of the Project class instance. Check out the previous note and find out how to get and use Project class instances.

Compilation compilation = project.GetCompilationAsync().Result;
Another way to get the semantic model is to call the Create method of the CSharpCompilation class. We'll use this way in code examples below.
SemanticModel model = compilation.GetSemanticModel(tree);
To get the semantic model itself, call the GetSemanticModel method of the compilation object and pass it a SyntaxTree object:

The semantic model provides access to so-called symbols. They allow you to get the necessary information about the entity itself - a property or a method. Symbols can be divided into two categories:
  • symbols for getting information about the entity itself.
  • symbols for getting information about the entity type.
Each symbol contains information about what type and namespace a particular element is defined in. We can find out where exactly an element was defined: in the source code that we have access to, or in an external library. In addition, we can get information about whether the analyzed element is static, virtual, or other. All this information is provided through the functionality of the basic ISymbol interface.

Let's look at this case. To perform the analysis, we need to understand whether the called method is overridden. In other words, we need to know whether it was marked with the override modifier when it was declared. Just in this case, we will need a symbol.
string codeStr =
  @"
  using System;
 
  public class ParentClass
  {
    virtual public void Mehtod1()
    {
      Console.WriteLine(""Hello from Parent"");
    }
  }
 
  public class ChildClass: ParentClass
  {
    public override void Mehtod1()
    {
      Console.WriteLine(""Hello from Child"");
    }
  }
 
  class Program
  {
    static void Main(string[] args)
      {
        ChildClass childClass = new ChildClass();
        childClass.Mehtod1();
      }
  }";
 
SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(codeStr);
 
var msCorLib = MetadataReference
               .CreateFromFile(typeof(object).Assembly.Location);
var compilation = CSharpCompilation.Create("MyCompilation",
    syntaxTrees: new[] { tree }, references: new[] { msCorLib });
var model = compilation.GetSemanticModel(tree);
 
var methodInvocSyntax = tree.GetRoot().DescendantNodes()
                            .OfType<InvocationExpressionSyntax>();
 
foreach (var methodInvocation in methodInvocSyntax)
{
  var methodSymbol = model.GetSymbolInfo(methodInvocation).Symbol;
  if (methodSymbol.IsOverride)
  {
    //Apply your additional logic for analyzing method.
  }
}
After we get the symbol for the method, we can access its IsOverride property. Then we can determine whether the method is marked with the override modifier.

Some readers may suggest other options how to determine if the method is overridden. And in doing so we should manage without using a semantic model.
... 
var methodDeclarsSyntax = tree.GetRoot().DescendantNodes()
                              .OfType<MethodDeclarationSyntax>();
... 
foreach (var methodDeclaration in methodDeclarsSyntax)
{
     bool isOverriden = methodDeclaration
                        .Modifiers.Any(modifier =>
                        modifier.IsKind(SyntaxKind.OverrideKeyword));
}
This method also works, but in fewer cases. Let's say, we got the syntax tree for a source file. But the method declaration was not made in this source file. This way, we won't be able to get declaration for the method we need. Here is another even more revealing case. If a called method is declared in an external library - there is no way to do without the use of the semantic model.

Getting information about an object. Specification of the symbol type


There is a number of derived types, that a programmer can cast to and get more specific information about the object. Such interfaces are IFieldSymbol, IPropertySymbol, IMethodSymbol and others. 

How can we find out whether the node is a constant field? For example, we can use casting to the IFieldSymbol interface and access the IsConst, field. If we use the IMethodSymbol interface, we can find out if this method returns any value. 

The Kind property is defined for symbols. This property returns enumeration elements of SymbolKind. That is, by using this property, we can see what we're working on - a local object, a field, a property, a build, etc.

Here is a small example how to use it:
string codeStr =
  @"
  public class MyClass
  {
    public string MyProperty { get; }
  }
 
  class Program
  {
    static void Main(string[] args)
    {
      MyClass myClass = new MyClass();
      myClass.MyProperty;
    }
  }";
 
SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(codeStr);
 
var msCorLib = MetadataReference
               .CreateFromFile(typeof(object).Assembly.Location);
 
var compilation = CSharpCompilation.Create("MyCompilation",
    syntaxTrees: new[] { tree }, references: new[] { msCorLib });
 
var model = compilation.GetSemanticModel(tree);
var propertyAccessSyntax = tree.GetRoot().DescendantNodes()
                             .OfType<MemberAccessExpressionSyntax>()
                             .First();
 
var symbol = model.GetSymbolInfo(propertyAccessSyntax).Symbol;
if (symbol.Kind == SymbolKind.Property)
{
    var pSymbol = (IPropertySymbol)symbol;
 
    var isReadOnly = pSymbol.IsReadOnly; //true
    var type = pSymbol.Type;             // System.String
}
After casting a symbol to IPropertySymbol, we can access fields that help to get additional information about MyProperty. We access the MyProperty field in the source file where this field declaration is. This means we can find out that MyProperty does not have a setter without the semantic model. If the field declaration is in another file or library, then we will inevitably access the semantic model.

How to get information about an object type


Use the ITypeSymbol interface when you need to get information about a type of an object that is represented by a node. To get the interface, use the method GetTypeInfo of the SemanticModel type object. In general, this method returns the TypeInfo structure that contains 2 important properties:

  • ConvertedType returns information about an expression type after the implicit casting. If there isn't any casting, the returned value will be similar to the one that is returned by the Type property.
  • Type returns the type of the expression given in the node. If it's not possible to get an expression type, we will get null. If the type cannot be defined because of some error, then the IErrorTypeSymbol interface is returned. 

Here is an example of how you can get the type of a property that is assigned a value.
string codeStr =
  @"
  public class MyClass
  {
    public string MyProperty { get; set; }
    
    public MyClass(string value)
    {
      MyProperty = value;
    }
  }";
 
SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(codeStr);
 
var msCorLib = MetadataReference
               .CreateFromFile(typeof(object).Assembly.Location);
var compilation = CSharpCompilation.Create("MyCompilation",
    syntaxTrees: new[] { tree }, references: new[] { msCorLib });
var model = compilation.GetSemanticModel(tree);
 
var assignmentExpr = tree.GetRoot().DescendantNodes()
                         .OfType<AssignmentExpressionSyntax>()
                         .First();
 
ExpressionSyntax left = assignmentExpr.Left;
 
var typeOfMyProperty = model.GetTypeInfo(left).Type;
When we use the ITypeSymbol interface that is returned by these properties, we can get all information about the type. This information is retrieved due to the access to the properties. Here is the list of some properties.
  • AllInterfaces - a list of all the interfaces that are implemented by the type. Interfaces implemented by the base types are also taken into account.
  • BaseType - a base type.
  • Interfaces - a list of interfaces implemented specifically by this type.
  • IsAnonymousType - information on whether the type is anonymous.

Some comments on the use of the semantic model


You have to pay a certain price if you turn to a semantic model. The fact is that tree traversal operations are faster than the semantic model retrieval operation. To get various symbols for nodes that belong to one syntax tree, you can get the semantic model only once. After you can access the same instance of the SemanticModelclass if needed.

For more information about the semantic model, I recommend using the following resources:


Summary


In this article we learned how to use semantic model to enhance abilities of our static analyzer. Using semantic model, we can access information about types of syntax nodes, find out necessary details about methods, properties, etc.

Author Credit

Article Type : Guest Article
Author : Ilya Gainulin
Tags : CSharp, Knowledge
Article Date : 04-02-2021
Article Publish Date : 13-04-2021
Note : All content of this article are copyright of their author.

Codingvila provides articles and blogs on web and software development for beginners as well as free Academic projects for final year students in Asp.Net, MVC, C#, Vb.Net, SQL Server, Angular Js, Android, PHP, Java, Python, Desktop Software Application and etc.

Thank you for your valuable time, to read this article, If you like this article, please share this article and post your valuable comments.

Once, you post your comment, we will review your posted comment and publish it. It may take a time around 24 business working hours.

Sometimes I not able to give detailed level explanation for your questions or comments, if you want detailed explanation, your can mansion your contact email id along with your question or you can do select given checkbox "Notify me" the time of write comment. So we can drop mail to you.

If you have any questions regarding this article/blog you can contact us on info.codingvila@gmail.com

sentiment_satisfied Emoticon