Semantyczny model dostarcza nam wiele informacji o kodzie, które zwykle uzyskuje się po kompilacji. Na przykład przeładowanie metod jest dużo łatwiejsze do określenia już po kompilacji. Oznacza to, że nie jest już to klasyczna, statyczna analiza kodu. Z tego względu, najpierw danych kod należy skompilować za pomocą:
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var compilation = CSharpCompilation.Create("TestAssembly", new[] { tree }, new[] { mscorlib }); SemanticModel model = compilation.GetSemanticModel(tree);
Posiadając semantyczny model, możemy dokonać tzw. analizy przepływu danych. Jak sama nazwa wskazuje, dzięki niej będziemy wiedzieli, co dzieje się z naszymi danymi, a w szczególności:
- Jakie zmienne są odczytywane
- Jakie zmienne są zapisywane
- Jakie zmienne są deklarowane
- Co dzieje się ze zmiennymi w wewnątrz i na zewnątrz bloku
Jak widać, w celu uzyskania modelu, wystarczy wywołać GetSemanticModel na kompilacji. Ponadto załóżmy, ze powyższy SyntacTree zawiera następujący kod:
SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public void TestMethod1(string a) { int localA=3,localB=5; if(a!=null) { localA++; } localB++; } } ");
Następnie, w celu dokonania analizy instrukcji warunkowej “if(a!=null)” wystarczy:
var node = tree.GetRoot() .DescendantNodes() .OfType<BlockSyntax>() .First() .DescendantNodes() .OfType<IfStatementSyntax>() .First(); DataFlowAnalysis dataFlowResult = model.AnalyzeDataFlow(node);
Obiekt DataFlowAnalysis zawiera następujące pola:
public abstract class DataFlowAnalysis { public abstract ImmutableArray<ISymbol> VariablesDeclared { get; } public abstract ImmutableArray<ISymbol> DataFlowsIn { get; } public abstract ImmutableArray<ISymbol> DataFlowsOut { get; } public abstract ImmutableArray<ISymbol> AlwaysAssigned { get; } public abstract ImmutableArray<ISymbol> ReadInside { get; } public abstract ImmutableArray<ISymbol> WrittenInside { get; } public abstract ImmutableArray<ISymbol> ReadOutside { get; } public abstract ImmutableArray<ISymbol> WrittenOutside { get; } public abstract ImmutableArray<ISymbol> Captured { get; } public abstract ImmutableArray<ISymbol> UnsafeAddressTaken { get; } public abstract bool Succeeded { get; } }
Jak widzimy, struktura zawiera mnóstwo pól opisujących co dzieje się z danymi we wskazanym bloku, w naszym przypadku w instrukcji warunkowej.
Na przykład WrittenInside zawiera zmienne, które są zapisywane w danym bloku. Dla powyższego przykładu jest to “localA”. VariablesDeclared będzie puste ponieważ nie deklarujemy żadnych zmiennych w powyższym IF. DataFlowsIn zawiera z kolei “localA” oraz “a” ponieważ to one uczestniczą w DataFlow.
SemanticModel jest przydatny jeszcze w kilku innych analizach, o których będę pisał w następnych postach.