Skip to content

Instantly share code, notes, and snippets.

@TheSnowfield
Last active February 1, 2022 08:25
Show Gist options
  • Save TheSnowfield/a6ec7474a0a845ea4570411d8500cfdc to your computer and use it in GitHub Desktop.
Save TheSnowfield/a6ec7474a0a845ea4570411d8500cfdc to your computer and use it in GitHub Desktop.
compile code in runtime
/// <summary>
/// Run code
/// </summary>
/// <returns></returns>
public static async Task<MessageBuilder> OnRunPlainCode(MessageChain message)
{
    var codeTemplate = @"
namespace DynamicCodeExecution {
    public static class Runnable {
        public static object? Run() { 
#nullable enable
            " + $"{message}" + @"
#nullable restore
        }
    }
}
";
    // Try compile the code
    if (Compiler.TryCompile(codeTemplate,
            out var context, out var assembly))
    {
        try
        {
            // Get type
            var type = assembly!.GetType("DynamicCodeExecution.Runnable");

#nullable enable
            // Run the code
            var cancelation = new CancellationTokenSource();
            cancelation.CancelAfter(new TimeSpan(0, 0, 0, 5));

            var task = new Task<object?>(() =>
            {
                var result = type!.GetMethod("Run")!.Invoke(null, null);
                return result;
            }, cancelation.Token);
#nullable restore
            try
            {
                // Wait for code return
                task.Start();
                await task.WaitAsync(cancelation.Token);

                return MessageBuilder.Eval(task.Result!.ToString());
            }
            catch (Exception e)
            {
                if (e is TaskCanceledException)
                {
                    return new MessageBuilder("Timeout while the code execution. (> 5000ms)");
                }
            }
        }

        // Any exceptions
        catch (Exception e)
        {
            return null;
        }

        // Unload assembly
        finally
        {
            context!.Unload();

            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }

    return null;
}
public static class Compiler
{
    public static bool TryCompile(string code,
        out AssemblyLoadContext context, out Assembly assembly)
    {
        // Make syntax tree
        var syntaxTree = CSharpSyntaxTree.ParseText(code);

        // Reference path
        var assemblyPath = Path.Combine(Path.GetDirectoryName
            (typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly.Location));

        // Code complation
        var compilation = CSharpCompilation.Create(null, new[] {syntaxTree},
            options: new CSharpCompilationOptions(
                usings: new[]
                {
                    "System",
                    "System.Text",
                    "System.Linq",
                    "System.Drawing",
                    "System.Collections",
                    "System.Numerics",
                    "System.Object",
                    "System.Nullable",
                },
                allowUnsafe: true,
                outputKind: OutputKind.DynamicallyLinkedLibrary
            ),
            references: new[]
            {
                $"{assemblyPath}/System.dll",
                $"{assemblyPath}/System.Drawing.dll",
                $"{assemblyPath}/System.Linq.dll",
                $"{assemblyPath}/System.Runtime.dll",
                $"{assemblyPath}/System.Private.CoreLib.dll",
                $"{assemblyPath}/System.Numerics.dll",
                $"{assemblyPath}/System.Text.Json.dll",
                $"{assemblyPath}/System.Text.Encoding.dll",
                $"{assemblyPath}/System.Text.RegularExpressions.dll",
            }.Select(r => MetadataReference.CreateFromFile(r)).ToArray()
        );
        {
            // Read pe stream
            using (var peStream = new MemoryStream())
            {
                // Compilation failed
                if (!compilation.Emit(peStream).Success)
                {
                    context = null;
                    assembly = null;
                    return false;
                }

                peStream.Seek(0, SeekOrigin.Begin);

                // Create load context
                context = new DynamicCodeContext();
                assembly = context.LoadFromStream(peStream);
                return true;
            }
        }
    }

    private class DynamicCodeContext : AssemblyLoadContext
    {
        public DynamicCodeContext() : base(isCollectible: true)
        {
        }

        protected override Assembly Load(AssemblyName assemblyName)
            => null;
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment