Анализ исходного кода C# с помощью Irony

Это то, что мы с моей командой решили сделать для нашего школьного проекта. Ну, на самом деле мы еще не решили, как парсить исходные файлы C#.

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

Отчет должен содержать только:

  • строковые литералы
  • имена методов
  • имена переменных
  • имена полей
  • так далее

Я отвечаю за просмотр этой библиотеки Irony. Честно говоря, я не знаю, как лучше всего упорядочить данные в чистый читаемый отчет. Я использую класс грамматики С#, упакованный в zip.

Есть ли какой-либо шаг, на котором я могу правильно идентифицировать каждый дочерний узел? (например: использование директив, объявление пространства имен, объявление класса и т. д., тело метода)

Любая помощь или совет будут очень признательны. Спасибо.

EDIT: Извините, я забыл сказать, что нам также нужно проанализировать вызовы методов.


person Community    schedule 28.09.2011    source источник
comment
Ирония дает вам AST, который вы затем можете пройти. Однако AST является частью кода, поэтому без знания фактического AST трудно сказать вам, как получить информацию.   -  person Rune FS    schedule 28.09.2011
comment
На самом деле не только АСТ. Текущая грамматика С#, которую они имеют, дает вам простое дерево. Постараюсь до вечера выложить несколько примеров.   -  person Yurii Hohan    schedule 28.09.2011


Ответы (2)


Ваша главная цель — освоить основы формальных языков. Хороший стартап можно найти здесь . В этой статье описывается способ использования Irony на примере грамматики простого числового калькулятора.

Предположим, вы хотите разобрать некий файл, содержащий код C#, путь к которому вы знаете:

private void ParseForLongMethods(string path)
    {
        _parser = new Parser(new CSharpGrammar());
        if (_parser == null || !_parser.Language.CanParse()) return;
        _parseTree = null;
        GC.Collect(); //to avoid disruption of perf times with occasional collections
        _parser.Context.SetOption(ParseOptions.TraceParser, true);
        try
        {
            string contents = File.ReadAllText(path);
            _parser.Parse(contents);//, "<source>");
        }
        catch (Exception ex)
        {
        }
        finally
        {
            _parseTree = _parser.Context.CurrentParseTree;
            TraverseParseTree();
        }
    }

А вот и сам метод обхода с подсчетом информации в узлах. На самом деле этот код подсчитывает количество операторов в каждом методе класса. Если у вас есть какие-либо вопросы, вы всегда можете задать мне

 private void TraverseParseTree()
        {
            if (_parseTree == null) return;
            ParseNodeRec(_parseTree.Root);
        }
        private void ParseNodeRec(ParseTreeNode node)
        {
            if (node == null) return;
            string functionName = "";
            if (node.ToString().CompareTo("class_declaration") == 0)
            {
                ParseTreeNode tmpNode = node.ChildNodes[2];
                currentClass = tmpNode.AstNode.ToString();
            }
            if (node.ToString().CompareTo("method_declaration") == 0)
            {
                foreach (var child in node.ChildNodes)
                {
                    if (child.ToString().CompareTo("qual_name_with_targs") == 0)
                    {
                        ParseTreeNode tmpNode = child.ChildNodes[0];
                        while (tmpNode.ChildNodes.Count != 0)
                        { tmpNode = tmpNode.ChildNodes[0]; }
                        functionName = tmpNode.AstNode.ToString();
                    }
                    if (child.ToString().CompareTo("method_body") == 0)  //method_declaration
                    {
                        int statementsCount = FindStatements(child);
                        //Register bad smell
                        if (statementsCount>(((LongMethodsOptions)this.Options).MaxMethodLength))
                        {
                            //function.StartPoint.Line
                            int functionLine = GetLine(functionName);
                            foundSmells.Add(new BadSmellRegistry(name, functionLine,currentFile,currentProject,currentSolution,false));
                        }
                    }
                }
            }
            foreach (var child in node.ChildNodes)
            { ParseNodeRec(child); }
        }
person Yurii Hohan    schedule 28.09.2011

Я не уверен, что это то, что вам нужно, но вы можете использовать пространства имен CodeDom и CodeDom.Compiler для компиляции кода C#, а затем анализировать результаты с помощью Reflection, например:

        // Create assamblly in Memory
        CodeSnippetCompileUnit code = new CodeSnippetCompileUnit(classCode);
        CSharpCodeProvider provider = new CSharpCodeProvider();
        CompilerResults results = provider.CompileAssemblyFromDom(compileParams, code);
        foreach(var type in results.CompiledAssembly)
        {
              // Your analysis go here
        }

Обновление: в VS2015 вы можете использовать новый компилятор C# (он же Roslyn), чтобы сделать то же самое для пример:

var root = (CompilationUnitSyntax)tree.GetRoot();
var compilation = CSharpCompilation.Create("HelloTDN")
            .AddReferences(references: new[] { MetadataReference.CreateFromAssembly(typeof(object).Assembly) })
            .AddSyntaxTrees(tree);
var model = compilation.GetSemanticModel(tree);
var nameInfo = model.GetSymbolInfo(root.Usings[0].Name);
var systemSymbol = (INamespaceSymbol)nameInfo.Symbol;
foreach (var ns in systemSymbol.GetNamespaceMembers())
{
   Console.WriteLine(ns.Name);
}
person Amittai Shapira    schedule 28.09.2011