Страницы

Поиск по вопросам

Показаны сообщения с ярлыком roslyn. Показать все сообщения
Показаны сообщения с ярлыком roslyn. Показать все сообщения

воскресенье, 29 марта 2020 г.

Как в Roslyn выполнить код с точки входа

#c_sharp #roslyn


Нужно сделать, чтобы пользователь мог выполнять C#-код на моем сервере (что-то вроде
dotnetfiddle.net). Приложение Asp.net Core.

Есть такой метод, который вполне нормально работает:

public static async Task> ExecuteScriptAsync(string code, IEnumerable
references,
    IEnumerable usings)
{
    var options = ScriptOptions.Default.WithReferences(references).WithImports(usings);

    return await CSharpScript.RunAsync(code, options);
}


Проблема метода ExecuteScript в том, что такой код он уже не будет выполнять: 

class Program 
{
    public static void Main() 
    {
        throw new System.Exception();   
    }
}


Как сделать чтобы код выполнялся как в консольном приложении, т.е. метод Main был
точкой входа?

Update

Нашел такой код. Срабатывает, но если в скрипт дописать Console.WriteLine("hi"),
то он ломается с ошибкой "Имя Console не существует в текущем контексте". 

var script = @"using System;
                public static class Program
                {
                    public static int Main(string[] args)
                    {
                        var x = 7 * 8;
                        return x;
                    }
                }";

var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
var refs = new List
{
    MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
    MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")),
    MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.dll")),
    MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Core.dll")),
    MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")),
    MetadataReference.CreateFromFile(Assembly.GetEntryAssembly().Location)
};

// Parse the script to a SyntaxTree
var syntaxTree = CSharpSyntaxTree.ParseText(script);
var options = new CSharpCompilationOptions(OutputKind.ConsoleApplication);
// Compile the SyntaxTree to a CSharpCompilation
var compilation = CSharpCompilation.Create("Script",
    new[] { syntaxTree },
    refs,
    new CSharpCompilationOptions(
        OutputKind.ConsoleApplication,
        optimizationLevel: OptimizationLevel.Release,
        assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default));

//CodeDomProvider codeDomProvider = new CodeDomProvider();
using (var outputStream = new MemoryStream())
using (var pdbStream = new MemoryStream())
{
    // Emit assembly to streams.
    var result = compilation.Emit(outputStream, pdbStream);
    if (!result.Success)
    {
        return;
    }

    // Load the emitted assembly.
    var assembly = Assembly.Load(outputStream.ToArray(), pdbStream.ToArray());

    // Invoke the entry point.
    var x = assembly.EntryPoint.Invoke(null, null);

    


Ответы

Ответ 1



Assembly.Load, тем более из массива байт - очень плохая идея, ведь она не дает возможности впоследствии выгрузить сборку из памяти, т.е. при работе в серверном приложении память рано или поздно исчерпается и сервер придется перезапускать. Кроме того, как ограничить права для запускаемого кода, чтобы он не разрушил вам систему? Если вы ориентируетесь под .NET Core, то домены приложений недоступны. Правильнее скомпилировать код в файл и запускать его в новом процессе под пользователем с ограниченными правами и перехватывать его вывод: using System; using System.Collections.Generic; using System.IO; using System.Diagnostics; using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; namespace RoslynTest { class Program { static void RunScript() { var script = @"using System; public static class Program { public static int Main(string[] args) { var x = 7 * 8; Console.WriteLine(x.ToString()); return x; } }"; var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); var refs = new List { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")), MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.dll")), MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Core.dll")), MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")), MetadataReference.CreateFromFile(Assembly.GetEntryAssembly().Location) }; // Parse the script to a SyntaxTree var syntaxTree = CSharpSyntaxTree.ParseText(script); var options = new CSharpCompilationOptions(OutputKind.ConsoleApplication); // Compile the SyntaxTree to a CSharpCompilation var compilation = CSharpCompilation.Create("Script", new[] { syntaxTree }, refs, new CSharpCompilationOptions( OutputKind.ConsoleApplication, optimizationLevel: OptimizationLevel.Release, assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default) ); var result = compilation.Emit("script.exe"); if (!result.Success) { throw new ApplicationException("Cannot compile script"); } ProcessStartInfo psi = new ProcessStartInfo(); psi.FileName = "script.exe"; psi.UseShellExecute = false; psi.RedirectStandardOutput = true; psi.RedirectStandardInput = true; psi.UserName = "Vasya"; psi.Password = "123"; var process = new Process(); using (process) { process.StartInfo = psi; process.Start(); while (!process.StandardOutput.EndOfStream) { string res = process.StandardOutput.ReadLine(); Console.WriteLine(res); } } } } } Код заточен под .NET Framework / Windows, но думаю, не составит труда переделать под .NET Core, так как все используемые библиотеки есть в .NET Standard. Запуск процессов от имени другого пользователя должен работать в .NET Core на всех ОС, начиная с .NET Core 2.1. Примечание. В .NET Core 3.0 появилась возможность выгрузки сборок из памяти, но это все еще не решает проблему обеспечения безопасности.

вторник, 10 декабря 2019 г.

Как получить список всех вызываемых методов в коде C# Roslyn

#c_sharp #roslyn


Как получить список всех используемых. Например 

using System;
using System.Collections.Generic;
using System.Text;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            String s = "sasdad";
            s.ToString();
            Console.WriteLine(""Hello, World!"");
        }
    }
}


Получить ToString.
    


Ответы

Ответ 1



Делаем вот как. Для начала создадим анализатор, как описано в этом вопросе. Нам нужно получить все вызовы методов, и вывести их. Это делается напрямую так: var workspace = MSBuildWorkspace.Create(); // подставьте путь к вашему проекту var solution = await workspace.OpenSolutionAsync(@"D:\HelloWorld.sln"); var project = solution.Projects.Single(); var compilation = await project.GetCompilationAsync(); // обходим все деревья (то есть, все файлы) foreach (var syntaxTree in compilation.SyntaxTrees) { var root = await syntaxTree.GetRootAsync(); var model = compilation.GetSemanticModel(syntaxTree); // получаем всё, что выглядеит как вызов метода var invocations = root.DescendantNodes().OfType(); foreach (var invocation in invocations) { var expression = invocation.Expression; // спрашиваем у семантического анализатора о типе выражения var symbol = model.GetSymbolInfo(expression).Symbol; if (symbol.Kind != SymbolKind.Method) continue; // выясняем дополнительнуыю информацию bool isStatic = symbol.IsStatic; var name = symbol.Name; var containingTypeName = symbol.ContainingType.ToDisplayString(); var location = invocation.GetLocation().GetLineSpan(); // и выводим Console.WriteLine($"{(isStatic ? "Static" : "Non-static")} call," + $" type = {containingTypeName}, method = {name}, location = {location}"); } } Например, для вот такого тестового кода: using System; namespace HelloWorld { class Program { static string g = string.Empty; static void Main(string[] args) { string[] strings = { "Hello ", "world" }; foreach (var s in strings) Console.Write(s.ToString() + s?.ToString()); for (int i = 0; i < strings.Length; i++) Console.Write(strings[i].ToUpper()); Console.WriteLine(); } } } получаем результат: Static call, type = System.Console, method = Write, location = Program.cs: (11,16)-(11,59) Non-static call, type = string, method = ToString, location = Program.cs: (11,30)-(11,42) Non-static call, type = string, method = ToString, location = Program.cs: (11,47)-(11,58) Static call, type = System.Console, method = Write, location = Program.cs: (13,16)-(13,41) Non-static call, type = string, method = ToUpper, location = Program.cs: (13,30)-(13,50) Static call, type = System.Console, method = WriteLine, location = Program.cs: (14,12)-(14,31)

суббота, 13 октября 2018 г.

Как получить список всех вызываемых методов в коде C# Roslyn

Как получить список всех используемых. Например
using System; using System.Collections.Generic; using System.Text;
namespace HelloWorld { class Program { static void Main(string[] args) { String s = "sasdad"; s.ToString(); Console.WriteLine(""Hello, World!""); } } }
Получить ToString.


Ответ

Делаем вот как. Для начала создадим анализатор, как описано в этом вопросе
Нам нужно получить все вызовы методов, и вывести их. Это делается напрямую так:
var workspace = MSBuildWorkspace.Create(); // подставьте путь к вашему проекту var solution = await workspace.OpenSolutionAsync(@"D:\HelloWorld.sln"); var project = solution.Projects.Single(); var compilation = await project.GetCompilationAsync();
// обходим все деревья (то есть, все файлы) foreach (var syntaxTree in compilation.SyntaxTrees) { var root = await syntaxTree.GetRootAsync(); var model = compilation.GetSemanticModel(syntaxTree); // получаем всё, что выглядеит как вызов метода var invocations = root.DescendantNodes().OfType(); foreach (var invocation in invocations) { var expression = invocation.Expression; // спрашиваем у семантического анализатора о типе выражения var symbol = model.GetSymbolInfo(expression).Symbol; if (symbol.Kind != SymbolKind.Method) continue; // выясняем дополнительнуыю информацию bool isStatic = symbol.IsStatic; var name = symbol.Name; var containingTypeName = symbol.ContainingType.ToDisplayString(); var location = invocation.GetLocation().GetLineSpan(); // и выводим Console.WriteLine($"{(isStatic ? "Static" : "Non-static")} call," + $" type = {containingTypeName}, method = {name}, location = {location}"); } }
Например, для вот такого тестового кода:
using System;
namespace HelloWorld { class Program { static string g = string.Empty; static void Main(string[] args) { string[] strings = { "Hello ", "world" }; foreach (var s in strings) Console.Write(s.ToString() + s?.ToString()); for (int i = 0; i < strings.Length; i++) Console.Write(strings[i].ToUpper()); Console.WriteLine(); } } }
получаем результат:
Static call, type = System.Console, method = Write, location = Program.cs: (11,16)-(11,59) Non-static call, type = string, method = ToString, location = Program.cs: (11,30)-(11,42) Non-static call, type = string, method = ToString, location = Program.cs: (11,47)-(11,58) Static call, type = System.Console, method = Write, location = Program.cs: (13,16)-(13,41) Non-static call, type = string, method = ToUpper, location = Program.cs: (13,30)-(13,50) Static call, type = System.Console, method = WriteLine, location = Program.cs: (14,12)-(14,31)