Разбор кода С# (как строка) и вставка дополнительных методов

У меня есть приложение С#, над которым я работаю, он загружает его код удаленно, а затем запускает его (ради аргумента можно предположить, что приложение защищено).

Код С#, но он отправляется как XML-документ, анализируется как строка, а затем скомпилируется и выполняется.

Теперь, что я хотел бы сделать - и у меня есть немного больше трудностей, чем я ожидал, - это возможность проанализировать весь документ и перед компиляцией вставить дополнительные команды после каждого выполнения строки.

Например, рассмотрим код:

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

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

            Log(i);
        }
    }
}

Что бы я хотел, после разбора больше похоже:

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();
        }
    }
}

Имейте в виду очевидные ловушки - я не могу просто иметь его после каждой полуколонии, потому что это не сработает в getter/setter, то есть:

Преобразование:

public string MyString { get; set; }

To:

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

не удастся. Как и объявления уровня класса, использование операторов и т.д. Кроме того, существует ряд случаев, когда я мог бы добавить в MyAdditionalMethod() после фигурных скобок - как в делегатах, сразу после операторов if или объявлений методов и т.д.

Итак, что я изучал в CodeDOM, и похоже, что это может быть решение, но сложно выяснить, с чего начать. Я в противном случае пытаюсь разобрать всю вещь и создать дерево, которое я могу проанализировать, хотя это немного сложно, учитывая количество случаев, которые мне нужно рассмотреть.

Кто-нибудь знает какие-либо другие решения, которые там есть?

Ответ 1

Есть несколько парсеров С#, там я бы рекомендовал использовать что-то из Mono или SharpDevelop, поскольку они должны быть в курсе последних событий. Я решил использовать NRefactory из SharpDevelop, если вы download в качестве источника для SharpDevelop есть демонстрация и некоторые UnitTests, которые являются хорошим вступлением в ее использование.

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);
            }
        }

    }
}

Вам нужно будет улучшить посетителя, чтобы обрабатывать больше дел, но это хороший старт.

В качестве альтернативы вы можете скомпилировать оригинал и выполнить некоторые манипуляции с использованием Cecil или попробовать некоторую библиотеку AOP, например PostSharp. Наконец, вы можете изучить .NET Profiling API.

Ответ 2

Вы можете использовать систему преобразования программ источника в исходное. Такой инструмент анализирует код, сборки и AST, позволяет применять преобразования, а затем восстанавливает текст из AST. Что отличает систему от источника к источнику, что вы можете писать преобразования в терминах синтаксиса исходного языка, а не фрактальной детали AST, что значительно упрощает их запись и понимание позже.

То, что вы хотите сделать, будет смоделировано с помощью довольно простого преобразования программы используя наш DMS Software Reengineering Toolkit:

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

Это правило не является "текстовой" заменой; скорее, он анализируется парсером, который обрабатывает целевой код, и потому фактически он представляет собой два АСТ, левую и правую сторону (разделенные синтаксисом "- > ". Кавычки не являются строковыми кавычками, они представляют собой кавычки вокруг синтаксиса целевого языка, чтобы отличать его от синтаксиса самого языка правил. То, что внутри кавычек, является языком целевого языка (например, С#) с экранами типа \s, которые представляют целые языковые элементы (в этом случае stmt в соответствии с грамматикой целевого языка (например, С#). Левая сторона говорит: "соответствовать любому утверждению s", поскольку s определяется как "stmt" в грамматике. Правая сторона говорит: "Замените инструкцию на блок, содержащий исходный оператор \s, и новый код, который вы хотите вставили".Это все сделано с точки зрения синтаксических деревьев, используя грамматику в качестве руководства, и не может применить преобразование ко всему, что не является выражением. [ Причина переписывания оператора в виде блока заключается в том, что таким образом правая сторона действительна там, где sta теги действительны, идите проверить свою грамматику.]

Как практический вопрос, вам нужно написать правила для обработки других особых случаев, но в основном это написано больше правил. Вам также необходимо упаковать парсер/трансформатор/prettyprinter в виде пакета, который требует некоторого процедурного клея. Это еще намного проще, чем пытаться написать код, чтобы надежно подниматься и опускаться по дереву, сопоставляя узлы, а затем разбивая эти узлы, чтобы получить то, что вы хотите. Лучше, когда ваша грамматика (неизменно) должна быть скорректирована, правила перезаписи пересматриваются в соответствии с пересмотренной грамматикой и продолжают работать; любое процессуальное дерево, восхождение, которое вы, возможно, делаете, почти наверняка будет нарушено.

Когда вы пишете все больше и больше преобразований, эта возможность становится все более и более ценной. И когда вам удастся с небольшим количеством преобразований, добавление становится быстро привлекательным.

См. эту техническую документацию для более подробного обсуждения того, как работает DMS, и как она используется для применения преобразования инструментов, например, вы хотите делать, в реальных инструментах. В этом документе описываются основные идеи инструментов тестирования, продаваемых Semantic Designs.

Ответ 4

Для синтаксического анализа вы можете использовать класс CSharpCodeProvider Parse().