2011-02-14 30 views
11

Tengo una aplicación C# en la que estoy trabajando que carga el código de forma remota y luego lo ejecuta (para mayor justificación, puede suponer que la aplicación es segura).Analizando el código C# (como cadena) e insertando métodos adicionales

El código es C#, pero se envía como un documento XML, se analiza como una cadena, y luego se compila y ejecuta.

Ahora, lo que me gustaría hacer, y estoy teniendo más dificultades de las que esperaba, es poder analizar todo el documento y, antes de compilar, insertar comandos adicionales después de cada ejecución de línea.

Por ejemplo, considere el código:

using System; 
using System.Collections.Generic; 
using System.Linq; 

namespace MyCode 
{ 
    static class MyProg 
    { 
     static void Run() 
     { 
      int i = 0; 
      i++; 

      Log(i); 
     } 
    } 
} 

lo que me gustaría, después del análisis es algo más parecido a:

using System; 
using System.Collections.Generic; 
using System.Linq; 

namespace MyCode 
{ 
    static class MyProg 
    { 
     static void Run() 
     { 
      int i = 0; 
      MyAdditionalMethod(); 
      i++; 
      MyAdditionalMethod(); 

      Log(i); 
      MyAdditionalMethod(); 
     } 
    } 
} 

Tenga en cuenta las dificultades obvias - no puedo tenerlo después de cada semi-colon, porque esto no funcionaría en un getter/setter, es decir:

conversión:

public string MyString { get; set; } 

Para:

public string MyString { get; MyAdditionalMethod(); set; MyAdditionalMethod(); } 

fracasarían. Al igual que las declaraciones a nivel de clase, el uso de instrucciones, etc. Además, hay una serie de casos en los que también podría agregar en MyAdditionalMethod() después de llaves, como en delegados, inmediatamente después de sentencias if, declaraciones de métodos, etc.

Entonces, lo que he estado buscando en CodeDOM, y parece que podría ser una solución, pero es difícil saber por dónde empezar. De lo contrario, estoy tratando de analizar todo y crear un árbol que pueda analizar, aunque es un poco difícil, teniendo en cuenta la cantidad de casos que debo considerar.

¿Alguien sabe alguna otra solución que esté disponible?

+2

"El código es C#, pero se envía como un documento XML, se analiza como una cadena" - Dejando a un lado cuán raro suena todo esto, ¿podemos ver una muestra del XML? Tal vez pueda inyectar sus métodos antes de analizarlos en una cadena. – mpen

+0

No controlo el documento XML, por lo que no puedo inyectar nada hasta que llegue y se analice. El XML solo contiene el código como el texto interno de un elemento, es decir. using System.IO; namespace MyApp { public class myClass {... AlishahNovin

Respuesta

7

Hay algunos analizadores C# que recomendarían usar algo de Mono o SharpDevelop, ya que deberían estar actualizados. Tuve una oportunidad usando NRefactory de SharpDevelop, si download la fuente de SharpDevelop hay una demostración y algunos UnitTests que son una buena introducción a su uso.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using ICSharpCode.NRefactory; 
using System.IO; 
using ICSharpCode.NRefactory.Ast; 
using ICSharpCode.NRefactory.Visitors; 
using ICSharpCode.NRefactory.PrettyPrinter; 

namespace Parse 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      string code = @"using System; 
      using System.Collections.Generic; 
      using System.Linq; 

      namespace MyCode 
      { 
       static class MyProg 
       { 
        static void Run() 
        { 
         int i = 0; 
         i++; 

         Log(i); 
        } 
       } 
      } 
      "; 

      IParser p = ParserFactory.CreateParser(SupportedLanguage.CSharp, new StringReader(code)); 
      p.Parse(); 

      //Output Original 
      CSharpOutputVisitor output = new CSharpOutputVisitor(); 
      output.VisitCompilationUnit(p.CompilationUnit, null); 
      Console.Write(output.Text); 

      //Add custom method calls 
      AddMethodVisitor v = new AddMethodVisitor(); 
      v.VisitCompilationUnit(p.CompilationUnit, null); 
      v.AddMethodCalls(); 
      output = new CSharpOutputVisitor(); 
      output.VisitCompilationUnit(p.CompilationUnit, null); 

      //Output result 
      Console.Write(output.Text); 
      Console.ReadLine(); 
     } 


    } 

    //The vistor adds method calls after visiting by storing the nodes in a dictionary. 
    public class AddMethodVisitor : ConvertVisitorBase 
    { 
     private IdentifierExpression member = new IdentifierExpression("MyAdditionalMethod"); 

     private Dictionary<INode, INode> expressions = new Dictionary<INode, INode>(); 

     private void AddNode(INode original) 
     { 
      expressions.Add(original, new ExpressionStatement(new InvocationExpression(member))); 
     } 

     public override object VisitExpressionStatement(ExpressionStatement expressionStatement, object data) 
     { 
      AddNode(expressionStatement); 
      return base.VisitExpressionStatement(expressionStatement, data); 
     } 

     public override object VisitLocalVariableDeclaration(LocalVariableDeclaration localVariableDeclaration, object data) 
     { 
      AddNode(localVariableDeclaration); 
      return base.VisitLocalVariableDeclaration(localVariableDeclaration, data); 
     } 

     public void AddMethodCalls() 
     { 
      foreach (var e in expressions) 
      { 
       InsertAfterSibling(e.Key, e.Value); 
      } 
     } 

    } 
} 

Necesitará mejorar al visitante para manejar más casos, pero es un buen comienzo.

Como alternativa, puede compilar el original y hacer algunas manipulaciones de IL usando Cecil o probar alguna biblioteca de AOP como PostSharp. Finalmente, puedes mirar en el .NET Profiling API.

1

Para el análisis se podría utilizar CSharpCodeProvider Class 's Parse().

+0

Creo que esto arroja una NotImplementedException. –

+0

@Simon: Incorrecto. La clase base (CodeDomProvider) arroja NotImplementedException.El método es reemplazado por clases derivadas (como CSharpCodeProvider) y, por lo tanto, no arroja tal excepción. –

+2

@Mike: ¿dónde has visto que CSharpCodeProvider anula CreateParser o Parse? –

3

Se puede usar un sistema de transformación de programas de fuente a fuente. Dicha herramienta analiza el código, las compilaciones y los AST, le permite aplicar transformaciones y luego regenera el texto del AST. Lo que hace agradable a un sistema de fuente a fuente es que puede escribir transformaciones en términos de la sintaxis del lenguaje de origen en lugar de los detalles de fractal del AST, lo que hace que sea mucho más fácil escribir y comprender más adelante.

Lo que se quiere hacer sería modelada por una muy simple transformación de programas utilizando nuestro DMS Software Reengineering Toolkit:

rule insert_post_statement_call(s: stmt): stmt -> stmt = 
    " \s " -> " { \s ; MyAdditionalMethod(); }"; 

Esta regla no es una sustitución "texto"; más bien, es analizado por el analizador sintáctico que procesa el código objetivo, y así, de hecho, representa dos AST, un lado izquierdo y uno derecho (separados por la sintaxis "->". Las comillas no son comillas; son citas en torno a la sintaxis del idioma de destino para diferenciarlo de la sintaxis del lenguaje de la regla en sí. Lo que está dentro de las comillas es el texto del lenguaje de destino (por ejemplo, C#) con escapes como \ s, que representan elementos del lenguaje completo (en este caso, stmt de acuerdo con la gramática del idioma meta (por ejemplo, C#). El lado izquierdo dice "hacer coincidir cualquier enunciado s" porque s se define como un "stmt" en la gramática. El lado derecho dice: "reemplace el enunciado con un bloque que contiene la declaración original \ s, y el nuevo código que desea insertar ". Todo esto se hace en términos de árboles de sintaxis utilizando la gramática como una guía, no puede aplicar la transformación a nada que no sea una declaración. [ La razón para reescribir el enunciado como un bloque es porque de esa manera el lado derecho es válido donde las declaraciones son válidas, revise su gramática.]

Como una cuestión práctica, tendrá que escribir reglas para manejar otros casos especiales, pero esto es sobre todo escribir más reglas. También necesita empaquetar el analizador/transformador/impresora bonita como un paquete que requiere algo de pegamento de procedimiento. Esto es mucho más fácil que tratar de escribir código para subir y bajar de manera fiable del árbol, hacer coincidir los nodos y luego destruir esos nodos para obtener lo que desea. Mejor, cuando tu gramática (invariablemente) tiene que ajustarse, las reglas de reescritura se reparan de acuerdo con la gramática revisada y aún funcionan; cualquiera que sea la escalada de árboles de procedimiento que pueda estar haciendo, está casi seguro garantizado para romperse.

A medida que escribe más y más transformaciones, esta capacidad se vuelve cada vez más valiosa. Y cuando tiene éxito con un pequeño número de transformaciones, agregar más se vuelve atractivo rápidamente.

Consulte this technical paper para obtener una explicación más detallada sobre cómo funciona DMS y cómo se usa para aplicar transformaciones de instrumentación, como lo que quiere hacer, en herramientas reales. Este documento describe las ideas básicas detrás de las herramientas de cobertura de prueba vendidas por Semantic Designs.

+1

Esto parece ser solo un anuncio de un producto que probablemente no necesite. El .Net Framework proporciona clases simples para la compilación sobre la marcha – TFD

+0

@TFD: del OP: "¿Alguien sabe alguna otra solución que esté disponible?" Gracias por el ding. –