C#

Erweitertes Parsen von Exceptions

Das semantische Modell von Roslyn hilft die einzelnen Symbole des Codes zu verstehen.

Namensräume mit C# analysieren
Eric Prouzet

Vor kurzem habe ich mithilfe von Roslyn gezeigt, wie ein Quellcode nach Ausnahmen durchsucht werden kann. Hierzu habe ich den Syntaxbaum von Roslyn verwendet. Dieser liefert Informationen zum lexikalischen und syntaktischen Aufbau eines Quellcodes. Möchte man weitere Details über einen Syntax-Knoten erhalten, so ist dieser aber nicht ausreichend. Im genannten Beispiel war es z.B. nicht möglich den Namensraum der geworfenen Ausnahmen zu ermitteln. Um an diese Informationen zu kommen wird das semantische Modell benötigt. Das semantische Modell wird von Roslyn während des Kompiliervorgangs erstellt.

Dies klingt allerdings komplizierter als es ist. So kann das semantische Modell durch Aufruf der Methode GetSemanticModelAsync() für ein Dokument abgerufen werden.

var filename = ...;
var ws = MSBuildWorkspace.Create();
var project = ws.OpenProjectAsync(filename).Result;
var document = project.Documents.First();
var model = document.GetSemanticModelAsync().Result;

Das semantische Modell besteht aus Symbolen. Ein Symbol entspricht dabei jeweils einem vom Compiler gefundenem Element, wie z.B. einer Klasse, Variablen oder Methode. Die enthaltenen Informationen unterscheiden sich zwar je nach Element, aber es gibt auch Informationen die bei allen Symbolen zur Verfügung stehen. Dies sind beispielsweise der Ort (Stelle im Quellcode bzw. in einem externen Verweis, sowie der umschließende Namensraum) oder Modifikatoren wie public, protected, static oder abstract. Weitere Informationen kommen dann je nach Symboltyp hinzu. So hat ein Symbol für eine Methode Informationen über die Übergabeparameter und dessen Rückgabewert.

Um das Symbol für einen Syntax-Knoten zu erhalten, reicht es eine Methode auf dem semantischen Modell aufzurufen. Je nach Art des Syntax-Knotens muss zwischen den beiden Methoden GetDeclaredSymbol() und GetSymbolInfo() unterschieden werden. Erstere dient zum Abrufen des Symbols für Deklarationen wie z.B. einer Klassen- oder Methodendeklaration. Die zweite Methode kommt dann zum Einsatz, wenn das Symbol für einen Ausdruck, wie einen Methodenaufruf, abgefragt werden soll.

Das kürzlich gezeigte Programm zur Auflistung von Ausnahmen lässt sich leicht, um die Fähigkeit den Namensraum anzuzeigen, erweitern. Hierzu muss zuerst die Klasse DocumentInfo um die Eigenschaft Model für das Speichern des SemanticModel erweitert werden:

public class DocumentInfo
{
  public SyntaxNode Node { get; set; }
  public SemanticModel Model { get; set; }
}

Diese Eigenschaft kann dann in der Methode OpenAllDocuments() mit dem entsprechenden Modell befüllt werden:

private static IEnumerable<DocumentInfo> OpenAllDocuments(
                             IEnumerable<Project> projects)
{
  var documents = projects.SelectMany(x => x.Documents);
  return documents.Select(x => new DocumentInfo {
      Node = x.GetSyntaxRootAsync().Result, 
      Model = x.GetSemanticModelAsync().Result});
}

Als letztes wird die Methode FindExceptions() angepasst. Diese soll nun über das Modell den Namensraum der Ausnahme ermitteln. Bisher wurde dort nur der Name über den Syntax-Knoten ermittelt:

...

foreach (var stmt in throwStatements)
{
  var objCreation = stmt.DescendantNodes().
       OfType<ObjectCreationExpressionSyntax>().FirstOrDefault();
  if (objCreation != null)
  {
    var model = element.Parent.Document.Model;
    var symbolInfo = model.GetSymbolInfo(objCreation);
    var type = symbolInfo.Symbol.ContainingType;
    nodeDesc.Add(type.ContainingNamespace + "." + type.Name);
  }
}

...

Natürlich ist der Quellcode des kompletten Programms wieder auf Github verfügbar. Ich hoffe, ich konnte einen kleinen Einblick in das semantische Modell geben.