Translate

domingo, 16 de abril de 2023

Analizando el flujo de datos con Roslyn

Esta API se puede usar para inspeccionar cómo se leen y escriben las variables dentro de un bloque de código determinado. Tal vez le gustaría crear una extensión de Visual Studio que capture y registre todas las asignaciones a una determinada variable. Se puede usar la API de análisis de flujo de datos para encontrar las declaraciones y un reescritor para registrarlas.

Para demostrar las capacidades de esta API, podemos analizar el bucle for en el siguiente código:


var tree = CSharpSyntaxTree.ParseText(@"

public class Sample

{

   public void Foo()

   {

        int[] outerArray = new int[10] { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4};

        for (int index = 0; index < 10; index++)

        {

             int[] innerArray = new int[10] { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4 };

             index = index + 2;

             outerArray[index – 1] = 5;

        }

   }

}");

 

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 forStatement = tree.GetRoot().DescendantNodes().OfType<ForStatementSyntax>().Single();

DataFlowAnalysis result = model.AnalyzeDataFlow(forStatement);


En este punto, tenemos acceso a un objeto DataFlowAnalysis.


Quizás la propiedad más importante de este objeto es Succeeded. Esto le indica si el análisis de flujo de datos se completó correctamente. En mi experiencia, la API ha sido bastante buena para lidiar con código semánticamente inválido. Ni las invocaciones a métodos faltantes ni el uso de variables no declaradas parecían hacer tropezar. La documentación señala que si la región analizada no abarca una sola expresión o declaración, es probable que el análisis falle.

El objeto DataFlowAnalysis expone una API bastante rica. Expone información sobre direcciones inseguras, variables locales capturadas por métodos anónimos y mucho más.

En nuestro caso, estamos interesados en las siguientes propiedades:

  • DataFlowAnalysis.AlwaysAssigned: el conjunto de variables locales para las que siempre se asigna un valor dentro de una región.
  • DataFlowAnalysis.ReadInside: el conjunto de variables locales que se leen dentro de una región.
  • DataFlowAnalysis.WrittenOutside: el conjunto de variables locales que se escriben fuera de una región.
  • DataFlowAnalysis.WrittenInside: el conjunto de variables locales que se escriben dentro de una región.
  • DataFlowAnalysis.VariablesDeclared: el conjunto de variables locales que se declaran dentro de una región. Tenga en cuenta que la región debe estar delimitada por el cuerpo de un método o el inicializador de un campo, por lo que los símbolos de parámetros nunca se incluyen en el resultado.


Los resultados del análisis son los siguientes:


AlwaysAssigned: index

index siempre se asigna a, ya que está contenido en el inicializador del bucle for, que se ejecuta incondicionalmente.

WrittenInside: index, innerArray

Tanto index como innerArray están claramente escritos dentro del bucle.

Un punto importante es que externalArray no. Mientras estamos mutando la matriz, no estamos mutando la referencia contenida dentro de la variable outsideArray. Por lo tanto, no aparece en esta lista.

WrittenOutside: outerArray, this

outsideArray está claramente escrito fuera del bucle for.

Sin embargo, me sorprendió que esto apareciera como un símbolo de parámetro dentro de la lista de WriteOutside. Parece como si esto se pasara como un parámetro a la clase y su miembro, lo que significa que también aparece aquí. Esto parece ser por diseño, aunque sospecho que la mayoría de los consumidores de esta API se sorprenderán y probablemente ignoren este valor.

ReadInside: index, outerArray

Está claro que el valor del índice se lee dentro del ciclo.

Me sorprendió que se considere que outsideArray se "lee" dentro del ciclo, ya que no estamos leyendo su valor directamente. Supongo que, técnicamente, primero debemos leer el valor de externalArray para calcular el desplazamiento y recuperar la dirección correcta para el elemento dado de la matriz. Así que estamos realizando una especie de "lectura implícita" dentro del ciclo aquí.


VariablesDeclared: index, innerArray

Esto es bastante sencillo. index se declara dentro del inicializador de bucle e innerArray dentro del cuerpo del bucle for.

La rareza general de la API de análisis de flujo de datos hace que no le vea mucha utilidad, se les ocurre un lugar para usarla?