diff --git a/source/Server/Compilation/Setups/CSharpSetup.cs b/source/Server/Compilation/Setups/CSharpSetup.cs index 4059195..31f2c45 100644 --- a/source/Server/Compilation/Setups/CSharpSetup.cs +++ b/source/Server/Compilation/Setups/CSharpSetup.cs @@ -43,7 +43,7 @@ namespace SharpLab.Server.Compilation.Setups { // ReSharper disable HeapView.ObjectAllocation.Evident options.CSharp.ParseOptions = new CSharpParseOptions(MaxLanguageVersion, preprocessorSymbols: PreprocessorSymbols).WithFeatures(_features); - options.CSharp.CompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true); + options.CSharp.CompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); options.CSharp.MetadataReferences = _references; // ReSharper restore HeapView.ObjectAllocation.Evident diff --git a/source/Server/Execution/Executor.cs b/source/Server/Execution/Executor.cs new file mode 100644 index 0000000..2c70aef --- /dev/null +++ b/source/Server/Execution/Executor.cs @@ -0,0 +1,69 @@ +using System; +using System.IO; +using System.Reflection; +using AppDomainToolkit; +using AshMind.Extensions; +using Microsoft.IO; +using Unbreakable; + +namespace SharpLab.Server.Execution { + public class Executor { + private readonly RecyclableMemoryStreamManager _memoryStreamManager; + + public Executor(RecyclableMemoryStreamManager memoryStreamManager) { + _memoryStreamManager = memoryStreamManager; + } + + public string Execute(Stream assemblyStream) { + using (var guardedStream = _memoryStreamManager.GetStream()) { + RuntimeGuardToken guardToken; + using (assemblyStream) { + guardToken = AssemblyGuard.Rewrite(assemblyStream, guardedStream); + } + + var currentSetup = AppDomain.CurrentDomain.SetupInformation; + using (var context = AppDomainContext.Create(new AppDomainSetup { + ApplicationBase = currentSetup.ApplicationBase, + PrivateBinPath = currentSetup.PrivateBinPath + })) { + context.LoadAssembly(LoadMethod.LoadFrom, Assembly.GetExecutingAssembly().GetAssemblyFile().FullName); + return RemoteFunc.Invoke(context.Domain, guardedStream, guardToken, Remote.Execute); + } + } + } + + private static class Remote { + public static string Execute(Stream assemblyStream, RuntimeGuardToken guardToken) { + try { + var assembly = Assembly.Load(ReadAllBytes(assemblyStream)); + var c = assembly.GetType("C"); + var m = c.GetMethod("M"); + + using (guardToken.Scope()) { + return m.Invoke(Activator.CreateInstance(c), null)?.ToString(); + } + } + catch (Exception ex) { + return ex.ToString(); + } + } + + private static byte[] ReadAllBytes(Stream stream) { + byte[] bytes; + if (stream is MemoryStream memoryStream) { + bytes = memoryStream.GetBuffer(); + if (bytes.Length != memoryStream.Length) + bytes = memoryStream.ToArray(); + return bytes; + } + + // we can't use ArrayPool here as this method is called in a temp AppDomain + bytes = new byte[stream.Length]; + if (stream.Read(bytes, 0, (int)stream.Length) != bytes.Length) + throw new NotSupportedException(); + + return bytes; + } + } + } +} diff --git a/source/Server/MirrorSharp/Internal/TargetNames.cs b/source/Server/MirrorSharp/Internal/TargetNames.cs new file mode 100644 index 0000000..9c3a0a3 --- /dev/null +++ b/source/Server/MirrorSharp/Internal/TargetNames.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharpLab.Server.MirrorSharp.Internal { + public static class TargetNames { + public const string Ast = "AST"; + public const string Run = "Run"; + } +} diff --git a/source/Server/MirrorSharp/SetOptionsFromClient.cs b/source/Server/MirrorSharp/SetOptionsFromClient.cs index 96f325d..534df0c 100644 --- a/source/Server/MirrorSharp/SetOptionsFromClient.cs +++ b/source/Server/MirrorSharp/SetOptionsFromClient.cs @@ -11,6 +11,10 @@ namespace SharpLab.Server.MirrorSharp { return false; session.SetTargetName(value); + //if (value == TargetNames.Run) { + // session.Roslyn.Project. + //} + return true; } } diff --git a/source/Server/MirrorSharp/SlowUpdate.cs b/source/Server/MirrorSharp/SlowUpdate.cs index d6e1e67..49f274f 100644 --- a/source/Server/MirrorSharp/SlowUpdate.cs +++ b/source/Server/MirrorSharp/SlowUpdate.cs @@ -12,34 +12,38 @@ using MirrorSharp.Advanced; using SharpLab.Server.Compilation; using SharpLab.Server.Decompilation; using SharpLab.Server.Decompilation.AstOnly; +using SharpLab.Server.Execution; +using SharpLab.Server.MirrorSharp.Internal; namespace SharpLab.Server.MirrorSharp { [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public class SlowUpdate : ISlowUpdateExtension { - private const string AstTargetName = "AST"; private readonly ICompiler _compiler; private readonly IReadOnlyDictionary _decompilers; - private readonly RecyclableMemoryStreamManager _memoryStreamManager; private readonly IReadOnlyDictionary _astTargets; + private readonly Executor _executor; + private readonly RecyclableMemoryStreamManager _memoryStreamManager; public SlowUpdate( ICompiler compiler, IReadOnlyCollection decompilers, - RecyclableMemoryStreamManager memoryStreamManager, - IReadOnlyCollection astTargets + IReadOnlyCollection astTargets, + Executor executor, + RecyclableMemoryStreamManager memoryStreamManager ) { _compiler = compiler; _decompilers = decompilers.ToDictionary(d => d.LanguageName); - _memoryStreamManager = memoryStreamManager; _astTargets = astTargets .SelectMany(t => t.SupportedLanguageNames.Select(n => (target: t, languageName: n))) .ToDictionary(x => x.languageName, x => x.target); + _executor = executor; + _memoryStreamManager = memoryStreamManager; } public async Task ProcessAsync(IWorkSession session, IList diagnostics, CancellationToken cancellationToken) { var targetName = session.GetTargetName(); - if (targetName == AstTargetName) { + if (targetName == TargetNames.Ast) { var astTarget = _astTargets.GetValueOrDefault(session.LanguageName); return await astTarget.GetAstAsync(session, cancellationToken).ConfigureAwait(false); } @@ -47,7 +51,7 @@ namespace SharpLab.Server.MirrorSharp { if (diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error)) return null; - if (!_decompilers.ContainsKey(targetName)) + if (targetName != TargetNames.Run && !_decompilers.ContainsKey(targetName)) throw new NotSupportedException($"Target '{targetName}' is not (yet?) supported by this branch."); MemoryStream stream = null; @@ -58,6 +62,9 @@ namespace SharpLab.Server.MirrorSharp { return null; } stream.Seek(0, SeekOrigin.Begin); + if (targetName == TargetNames.Run) + return _executor.Execute(stream); + // it's fine not to Dispose() here -- MirrorSharp will dispose it after calling WriteResult() return stream; } @@ -74,12 +81,17 @@ namespace SharpLab.Server.MirrorSharp { } var targetName = session.GetTargetName(); - if (session.GetTargetName() == AstTargetName) { + if (targetName == TargetNames.Ast) { var astTarget = _astTargets.GetValueOrDefault(session.LanguageName); astTarget.SerializeAst(result, writer, session); return; } + if (targetName == TargetNames.Run) { + writer.WriteValue((string)result); + return; + } + var decompiler = _decompilers[targetName]; using (var stream = (Stream)result) using (var stringWriter = writer.OpenString()) { diff --git a/source/Server/Server.csproj b/source/Server/Server.csproj index a77b0eb..5284c97 100644 --- a/source/Server/Server.csproj +++ b/source/Server/Server.csproj @@ -25,6 +25,7 @@ + diff --git a/source/Server/ServerModule.cs b/source/Server/ServerModule.cs index a286e4c..485c66f 100644 --- a/source/Server/ServerModule.cs +++ b/source/Server/ServerModule.cs @@ -9,6 +9,7 @@ using SharpLab.Server.Compilation.Internal; using SharpLab.Server.Compilation.Setups; using SharpLab.Server.Decompilation; using SharpLab.Server.Decompilation.AstOnly; +using SharpLab.Server.Execution; using SharpLab.Server.MirrorSharp; namespace SharpLab.Server { @@ -53,6 +54,8 @@ namespace SharpLab.Server { builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); + builder.RegisterType().AsSelf().SingleInstance(); + builder.RegisterInstance(new RecyclableMemoryStreamManager()) .AsSelf(); diff --git a/source/WebApp/index.html b/source/WebApp/index.html index 2797a3f..bf02d81 100644 --- a/source/WebApp/index.html +++ b/source/WebApp/index.html @@ -82,11 +82,16 @@
diff --git a/source/WebApp/js/helpers/targets.js b/source/WebApp/js/helpers/targets.js index 48eeb8b..6b0da13 100644 --- a/source/WebApp/js/helpers/targets.js +++ b/source/WebApp/js/helpers/targets.js @@ -5,5 +5,6 @@ export default Object.freeze({ vb: languages.vb, il: 'IL', asm: 'JIT ASM', - ast: 'AST' + ast: 'AST', + run: 'Run' }); \ No newline at end of file diff --git a/source/WebApp/js/state/handlers/url.js b/source/WebApp/js/state/handlers/url.js index eec26b1..9e1ad11 100644 --- a/source/WebApp/js/state/handlers/url.js +++ b/source/WebApp/js/state/handlers/url.js @@ -12,7 +12,8 @@ const languageAndTargetMap = { [languages.fsharp]: 'fs', [targets.il]: 'il', [targets.asm]: 'asm', - [targets.ast]: 'ast' + [targets.ast]: 'ast', + [targets.run]: 'run' }; const languageAndTargetMapReverse = mapObject(languageAndTargetMap, (key, value) => [value, key]); const targetMapReverseV1 = mapObject(languageAndTargetMapReverse, (key, value) => ['>' + key, value]); // eslint-disable-line prefer-template