Разбор кода С# (как строка) и вставка дополнительных методов
У меня есть приложение С#, над которым я работаю, он загружает его код удаленно, а затем запускает его (ради аргумента можно предположить, что приложение защищено).
Код С#, но он отправляется как 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.
Ответ 3
Вам нужно использовать деревья выражений. Некоторая полезная информация из MSDN для начала:
Ответ 4
Для синтаксического анализа вы можете использовать класс CSharpCodeProvider Parse().
Ответ 5
После разбора текста на этой странице есть подробная информация о компиляции и выполнении кода динамически: http://www.west-wind.com/presentations/dynamiccode/dynamiccode.htm