From f1907e1627be46c0210c47841a6f83ad8b4d0b0f Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Wed, 18 Aug 2021 21:21:09 +0300 Subject: [PATCH 01/18] AssemblyLoadPal is used instead of AppDomain in order to cover both AppDomain and AssemblyLoadContext scenarios. --- .../Dotnet.Script.Core.csproj | 1 + src/Dotnet.Script.Core/ScriptRunner.cs | 18 ++++++++++-------- src/Dotnet.Script.Tests/ScriptRunnerTests.cs | 3 ++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index 24a97b2f..665368de 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -23,6 +23,7 @@ + diff --git a/src/Dotnet.Script.Core/ScriptRunner.cs b/src/Dotnet.Script.Core/ScriptRunner.cs index 38940695..d4efedfe 100644 --- a/src/Dotnet.Script.Core/ScriptRunner.cs +++ b/src/Dotnet.Script.Core/ScriptRunner.cs @@ -7,6 +7,7 @@ using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.DependencyModel.Logging; using Dotnet.Script.DependencyModel.Runtime; +using Gapotchenko.FX.Reflection; using Microsoft.CodeAnalysis.CSharp.Scripting.Hosting; using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting.Hosting; @@ -30,13 +31,15 @@ public ScriptRunner(ScriptCompiler scriptCompiler, LogFactory logFactory, Script public async Task Execute(string dllPath, IEnumerable commandLineArgs) { + var assemblyLoaderPal = AssemblyLoadPal.ForCurrentAppDomain; + var runtimeDeps = ScriptCompiler.RuntimeDependencyResolver.GetDependenciesForLibrary(dllPath); var runtimeDepsMap = ScriptCompiler.CreateScriptDependenciesMap(runtimeDeps); - var assembly = Assembly.LoadFrom(dllPath); // this needs to be called prior to 'AppDomain.CurrentDomain.AssemblyResolve' event handler added + var assembly = assemblyLoaderPal.LoadFrom(dllPath); // this needs to be called prior to 'AppDomain.CurrentDomain.AssemblyResolve' event handler added - Assembly OnResolve(object sender, ResolveEventArgs args) => ResolveAssembly(args, runtimeDepsMap); + Assembly OnResolve(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args) => ResolveAssembly(sender, args, runtimeDepsMap); - AppDomain.CurrentDomain.AssemblyResolve += OnResolve; + assemblyLoaderPal.Resolving += OnResolve; try { var type = assembly.GetType("Submission#0"); @@ -64,7 +67,7 @@ public async Task Execute(string dllPath, IEnumerable } finally { - AppDomain.CurrentDomain.AssemblyResolve -= OnResolve; + assemblyLoaderPal.Resolving -= OnResolve; } } @@ -97,12 +100,11 @@ public virtual async Task Execute(ScriptCompilationCont return ProcessScriptState(scriptResult); } - internal Assembly ResolveAssembly(ResolveEventArgs args, Dictionary runtimeDepsMap) + internal Assembly ResolveAssembly(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args, Dictionary runtimeDepsMap) { - var assemblyName = new AssemblyName(args.Name); - var result = runtimeDepsMap.TryGetValue(assemblyName.Name, out RuntimeAssembly runtimeAssembly); + var result = runtimeDepsMap.TryGetValue(args.Name.Name, out RuntimeAssembly runtimeAssembly); if (!result) return null; - var loadedAssembly = Assembly.LoadFrom(runtimeAssembly.Path); + var loadedAssembly = sender.LoadFrom(runtimeAssembly.Path); return loadedAssembly; } diff --git a/src/Dotnet.Script.Tests/ScriptRunnerTests.cs b/src/Dotnet.Script.Tests/ScriptRunnerTests.cs index 913ad33d..b97197f5 100644 --- a/src/Dotnet.Script.Tests/ScriptRunnerTests.cs +++ b/src/Dotnet.Script.Tests/ScriptRunnerTests.cs @@ -3,6 +3,7 @@ using Dotnet.Script.Core; using Dotnet.Script.DependencyModel.Runtime; using Dotnet.Script.Shared.Tests; +using Gapotchenko.FX.Reflection; using Moq; using Xunit; @@ -15,7 +16,7 @@ public void ResolveAssembly_ReturnsNull_WhenRuntimeDepsMapDoesNotContainAssembly { var scriptRunner = CreateScriptRunner(); - var result = scriptRunner.ResolveAssembly(new ResolveEventArgs("AnyAssemblyName"), new Dictionary()); + var result = scriptRunner.ResolveAssembly(AssemblyLoadPal.ForCurrentAppDomain, new AssemblyLoadPal.ResolvingEventArgs(null, "AnyAssemblyName"), new Dictionary()); Assert.Null(result); } From 79066f115fe0e89c9bb1ea480ef02c570ec0b37a Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Thu, 19 Aug 2021 11:06:52 +0300 Subject: [PATCH 02/18] ScriptRunner can be configured with a custom AssemblyLoadContext. --- .../Dotnet.Script.Core.csproj | 4 +++- src/Dotnet.Script.Core/ScriptRunner.cs | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index 665368de..4080e72c 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -4,7 +4,7 @@ A cross platform library allowing you to run C# (CSX) scripts with support for debugging and inline NuGet packages. Based on Roslyn. 1.1.0 filipw - netstandard2.0 + netstandard2.0;netcoreapp2.1 Dotnet.Script.Core Dotnet.Script.Core script;csx;csharp;roslyn @@ -16,6 +16,7 @@ false false false + 9.0 true ../dotnet-script.snk @@ -23,6 +24,7 @@ + diff --git a/src/Dotnet.Script.Core/ScriptRunner.cs b/src/Dotnet.Script.Core/ScriptRunner.cs index d4efedfe..260b8127 100644 --- a/src/Dotnet.Script.Core/ScriptRunner.cs +++ b/src/Dotnet.Script.Core/ScriptRunner.cs @@ -3,6 +3,9 @@ using System.Collections.Immutable; using System.Linq; using System.Reflection; +#if NETCOREAPP +using System.Runtime.Loader; +#endif using System.Threading.Tasks; using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.DependencyModel.Logging; @@ -29,9 +32,23 @@ public ScriptRunner(ScriptCompiler scriptCompiler, LogFactory logFactory, Script _scriptEnvironment = ScriptEnvironment.Default; } +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif + public async Task Execute(string dllPath, IEnumerable commandLineArgs) { - var assemblyLoaderPal = AssemblyLoadPal.ForCurrentAppDomain; +#if NETCOREAPP + var assemblyLoadContext = AssemblyLoadContext; + var assemblyLoaderPal = assemblyLoadContext != null ? new AssemblyLoadPal(assemblyLoadContext) : AssemblyLoadPal.ForCurrentAppDomain; +#else + var assemblyLoaderPal = AssemblyLoadPal.ForCurrentAppDomain; +#endif var runtimeDeps = ScriptCompiler.RuntimeDependencyResolver.GetDependenciesForLibrary(dllPath); var runtimeDepsMap = ScriptCompiler.CreateScriptDependenciesMap(runtimeDeps); From 11e800cb27455784dba53c45d90368ae7656ddf9 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Thu, 19 Aug 2021 11:09:11 +0300 Subject: [PATCH 03/18] ScriptRunner refactoring related to AssemblyLoadContext support. --- src/Dotnet.Script.Core/ScriptRunner.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Dotnet.Script.Core/ScriptRunner.cs b/src/Dotnet.Script.Core/ScriptRunner.cs index 260b8127..31d09583 100644 --- a/src/Dotnet.Script.Core/ScriptRunner.cs +++ b/src/Dotnet.Script.Core/ScriptRunner.cs @@ -45,18 +45,18 @@ public async Task Execute(string dllPath, IEnumerable { #if NETCOREAPP var assemblyLoadContext = AssemblyLoadContext; - var assemblyLoaderPal = assemblyLoadContext != null ? new AssemblyLoadPal(assemblyLoadContext) : AssemblyLoadPal.ForCurrentAppDomain; + var assemblyLoadPal = assemblyLoadContext != null ? new AssemblyLoadPal(assemblyLoadContext) : AssemblyLoadPal.ForCurrentAppDomain; #else - var assemblyLoaderPal = AssemblyLoadPal.ForCurrentAppDomain; + var assemblyLoadPal = AssemblyLoadPal.ForCurrentAppDomain; #endif var runtimeDeps = ScriptCompiler.RuntimeDependencyResolver.GetDependenciesForLibrary(dllPath); var runtimeDepsMap = ScriptCompiler.CreateScriptDependenciesMap(runtimeDeps); - var assembly = assemblyLoaderPal.LoadFrom(dllPath); // this needs to be called prior to 'AppDomain.CurrentDomain.AssemblyResolve' event handler added + var assembly = assemblyLoadPal.LoadFrom(dllPath); // this needs to be called prior to 'AssemblyLoadPal.Resolving' event handler added Assembly OnResolve(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args) => ResolveAssembly(sender, args, runtimeDepsMap); - assemblyLoaderPal.Resolving += OnResolve; + assemblyLoadPal.Resolving += OnResolve; try { var type = assembly.GetType("Submission#0"); @@ -84,7 +84,7 @@ public async Task Execute(string dllPath, IEnumerable } finally { - assemblyLoaderPal.Resolving -= OnResolve; + assemblyLoadPal.Resolving -= OnResolve; } } @@ -117,11 +117,11 @@ public virtual async Task Execute(ScriptCompilationCont return ProcessScriptState(scriptResult); } - internal Assembly ResolveAssembly(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args, Dictionary runtimeDepsMap) + internal Assembly ResolveAssembly(AssemblyLoadPal pal, AssemblyLoadPal.ResolvingEventArgs args, Dictionary runtimeDepsMap) { var result = runtimeDepsMap.TryGetValue(args.Name.Name, out RuntimeAssembly runtimeAssembly); if (!result) return null; - var loadedAssembly = sender.LoadFrom(runtimeAssembly.Path); + var loadedAssembly = pal.LoadFrom(runtimeAssembly.Path); return loadedAssembly; } From 5b765606f0aacf4f77710847a1655540b5db9079 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Thu, 19 Aug 2021 13:30:40 +0300 Subject: [PATCH 04/18] AssemblyLoadContext: assembly loader, downstream propagation via contextual reflection for .NET Core 3.0+ hosts. --- src/Dotnet.Script.Core/Dotnet.Script.Core.csproj | 2 +- src/Dotnet.Script.Core/ScriptRunner.cs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index 4080e72c..f2749093 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -4,7 +4,7 @@ A cross platform library allowing you to run C# (CSX) scripts with support for debugging and inline NuGet packages. Based on Roslyn. 1.1.0 filipw - netstandard2.0;netcoreapp2.1 + netstandard2.0;netcoreapp2.1;netcoreapp3.1 Dotnet.Script.Core Dotnet.Script.Core script;csx;csharp;roslyn diff --git a/src/Dotnet.Script.Core/ScriptRunner.cs b/src/Dotnet.Script.Core/ScriptRunner.cs index 31d09583..0667c5c0 100644 --- a/src/Dotnet.Script.Core/ScriptRunner.cs +++ b/src/Dotnet.Script.Core/ScriptRunner.cs @@ -54,6 +54,15 @@ public async Task Execute(string dllPath, IEnumerable var runtimeDepsMap = ScriptCompiler.CreateScriptDependenciesMap(runtimeDeps); var assembly = assemblyLoadPal.LoadFrom(dllPath); // this needs to be called prior to 'AssemblyLoadPal.Resolving' event handler added +#if NETCOREAPP + using var assemblyAutoLoader = new AssemblyAutoLoader(assemblyLoadContext); + assemblyAutoLoader.AddAssembly(assembly); +#endif + +#if NETCOREAPP3_0_OR_GREATER + using var contextualReflectionScope = assemblyLoadContext.EnterContextualReflection(); +#endif + Assembly OnResolve(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args) => ResolveAssembly(sender, args, runtimeDepsMap); assemblyLoadPal.Resolving += OnResolve; From a81c8ef0c41bfdc2660432463998c2e03b9e93f2 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Thu, 19 Aug 2021 13:37:11 +0300 Subject: [PATCH 05/18] Custom assembly load context can be null and should not be used in this case. --- src/Dotnet.Script.Core/ScriptRunner.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Dotnet.Script.Core/ScriptRunner.cs b/src/Dotnet.Script.Core/ScriptRunner.cs index 0667c5c0..23d6ba1a 100644 --- a/src/Dotnet.Script.Core/ScriptRunner.cs +++ b/src/Dotnet.Script.Core/ScriptRunner.cs @@ -55,12 +55,12 @@ public async Task Execute(string dllPath, IEnumerable var assembly = assemblyLoadPal.LoadFrom(dllPath); // this needs to be called prior to 'AssemblyLoadPal.Resolving' event handler added #if NETCOREAPP - using var assemblyAutoLoader = new AssemblyAutoLoader(assemblyLoadContext); - assemblyAutoLoader.AddAssembly(assembly); + using var assemblyAutoLoader = assemblyLoadContext != null ? new AssemblyAutoLoader(assemblyLoadContext) : null; + assemblyAutoLoader?.AddAssembly(assembly); #endif #if NETCOREAPP3_0_OR_GREATER - using var contextualReflectionScope = assemblyLoadContext.EnterContextualReflection(); + using var contextualReflectionScope = assemblyLoadContext != null ? assemblyLoadContext.EnterContextualReflection() : default; #endif Assembly OnResolve(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args) => ResolveAssembly(sender, args, runtimeDepsMap); From e71073995eb8d2a7b99d8a216a999fc56104ea30 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Thu, 19 Aug 2021 14:06:43 +0300 Subject: [PATCH 06/18] Gluing and propagating custom AssemblyLoadContext through the higher API levels. --- .../Commands/ExecuteCodeCommand.cs | 7 ++++++- .../Commands/ExecuteCodeCommandOptions.cs | 12 ++++++++++++ .../Commands/ExecuteLibraryCommand.cs | 7 ++++++- .../Commands/ExecuteLibraryCommandOptions.cs | 13 +++++++++++++ .../Commands/ExecuteScriptCommand.cs | 15 ++++++++------- .../Commands/ExecuteScriptCommandOptions.cs | 12 ++++++++++++ 6 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs index 688540b0..b8b1b754 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs @@ -23,7 +23,12 @@ public async Task Execute(ExecuteCodeCommandOptions options) var sourceText = SourceText.From(options.Code); var context = new ScriptContext(sourceText, options.WorkingDirectory ?? Directory.GetCurrentDirectory(), options.Arguments, null, options.OptimizationLevel, ScriptMode.Eval, options.PackageSources); var compiler = GetScriptCompiler(!options.NoCache, _logFactory); - var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole); + var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; return await runner.Execute(context); } diff --git a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommandOptions.cs b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommandOptions.cs index b1bdf40b..11ebf3a7 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommandOptions.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommandOptions.cs @@ -1,4 +1,7 @@ using Microsoft.CodeAnalysis; +#if NETCOREAPP +using System.Runtime.Loader; +#endif namespace Dotnet.Script.Core.Commands { @@ -20,5 +23,14 @@ public ExecuteCodeCommandOptions(string code, string workingDirectory, string[] public OptimizationLevel OptimizationLevel { get; } public bool NoCache { get; } public string[] PackageSources { get; } + +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs index d7350e82..c406287d 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs @@ -26,7 +26,12 @@ public async Task Execute(ExecuteLibraryCommandOptions options var absoluteFilePath = options.LibraryPath.GetRootedPath(); var compiler = GetScriptCompiler(!options.NoCache, _logFactory); - var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole); + var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; var result = await runner.Execute(absoluteFilePath, options.Arguments); return result; } diff --git a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommandOptions.cs b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommandOptions.cs index 2c0f4796..a3c3cb7e 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommandOptions.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommandOptions.cs @@ -1,3 +1,7 @@ +#if NETCOREAPP +using System.Runtime.Loader; +#endif + namespace Dotnet.Script.Core.Commands { public class ExecuteLibraryCommandOptions @@ -12,5 +16,14 @@ public ExecuteLibraryCommandOptions(string libraryPath, string[] arguments, bool public string LibraryPath { get; } public string[] Arguments { get; } public bool NoCache { get; } + +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs index 470a4e52..278ff42c 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs @@ -32,7 +32,14 @@ public async Task Run(ExecuteScriptCommandOptions optio } var pathToLibrary = GetLibrary(options); - return await ExecuteLibrary(pathToLibrary, options.Arguments, options.NoCache); + + var libraryOptions = new ExecuteLibraryCommandOptions(pathToLibrary, options.Arguments, options.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; + return await new ExecuteLibraryCommand(_scriptConsole, _logFactory).Execute(libraryOptions); } private async Task DownloadAndRunCode(ExecuteScriptCommandOptions executeOptions) @@ -124,11 +131,5 @@ public bool TryGetHash(string cacheFolder, out string hash) hash = File.ReadAllText(pathToHashFile); return true; } - - private async Task ExecuteLibrary(string pathToLibrary, string[] arguments, bool noCache) - { - var options = new ExecuteLibraryCommandOptions(pathToLibrary, arguments, noCache); - return await new ExecuteLibraryCommand(_scriptConsole, _logFactory).Execute(options); - } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteScriptCommandOptions.cs b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommandOptions.cs index 656cb74d..93577f01 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteScriptCommandOptions.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommandOptions.cs @@ -1,4 +1,7 @@ using Microsoft.CodeAnalysis; +#if NETCOREAPP +using System.Runtime.Loader; +#endif namespace Dotnet.Script.Core.Commands { @@ -20,5 +23,14 @@ public ExecuteScriptCommandOptions(ScriptFile file, string[] arguments, Optimiza public string[] PackageSources { get; } public bool IsInteractive { get; } public bool NoCache { get; } + +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif } } \ No newline at end of file From d8f58a083dfa9af899a435d60f5ee83de16292a3 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Thu, 19 Aug 2021 18:01:38 +0300 Subject: [PATCH 07/18] Script files are executed in isolated assembly load context. --- .gitignore | 1 + src/Dotnet.Script/IsolatedAssemblyLoadContext.cs | 10 ++++++++++ src/Dotnet.Script/Program.cs | 5 ++++- 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 src/Dotnet.Script/IsolatedAssemblyLoadContext.cs diff --git a/.gitignore b/.gitignore index e9ec90a2..860e9507 100644 --- a/.gitignore +++ b/.gitignore @@ -265,3 +265,4 @@ project.json /build/dotnet-script /dotnet-script /.vscode +/src/Dotnet.Script/Properties/launchSettings.json diff --git a/src/Dotnet.Script/IsolatedAssemblyLoadContext.cs b/src/Dotnet.Script/IsolatedAssemblyLoadContext.cs new file mode 100644 index 00000000..67ebe677 --- /dev/null +++ b/src/Dotnet.Script/IsolatedAssemblyLoadContext.cs @@ -0,0 +1,10 @@ +using System.Reflection; +using System.Runtime.Loader; + +namespace Dotnet.Script +{ + sealed class IsolatedAssemblyLoadContext : AssemblyLoadContext + { + protected override Assembly Load(AssemblyName assemblyName) => null; + } +} diff --git a/src/Dotnet.Script/Program.cs b/src/Dotnet.Script/Program.cs index 54b2c196..62f3e6a2 100644 --- a/src/Dotnet.Script/Program.cs +++ b/src/Dotnet.Script/Program.cs @@ -245,7 +245,10 @@ private static int Wain(string[] args) packageSources.Values?.ToArray(), interactive.HasValue(), nocache.HasValue() - ); + ) + { + AssemblyLoadContext = new IsolatedAssemblyLoadContext() + }; var fileCommand = new ExecuteScriptCommand(ScriptConsole.Default, logFactory); return await fileCommand.Run(fileCommandOptions); From aecc8d626a364d6064b12786acf8f2336de2941a Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Fri, 20 Aug 2021 19:02:38 +0300 Subject: [PATCH 08/18] Implemented full assembly isolation support at API level. --- .../Commands/ExecuteCodeCommand.cs | 13 +- .../Commands/ExecuteInteractiveCommand.cs | 7 +- .../ExecuteInteractiveCommandOptions.cs | 15 +- .../Commands/ExecuteLibraryCommand.cs | 13 +- .../Commands/ExecuteScriptCommand.cs | 7 +- .../Commands/PublishCommand.cs | 14 +- .../Commands/PublishCommandOptions.cs | 12 ++ .../Dotnet.Script.Core.csproj | 2 +- .../ScriptAssemblyLoadContext.cs | 140 ++++++++++++++++++ src/Dotnet.Script.Core/ScriptCompiler.cs | 26 +++- src/Dotnet.Script.Core/ScriptRunner.cs | 51 +++++-- src/Dotnet.Script.Tests/ScriptRunnerTests.cs | 3 +- 12 files changed, 265 insertions(+), 38 deletions(-) create mode 100644 src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs diff --git a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs index b8b1b754..b9b6b039 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs @@ -22,7 +22,12 @@ public async Task Execute(ExecuteCodeCommandOptions options) { var sourceText = SourceText.From(options.Code); var context = new ScriptContext(sourceText, options.WorkingDirectory ?? Directory.GetCurrentDirectory(), options.Arguments, null, options.OptimizationLevel, ScriptMode.Eval, options.PackageSources); - var compiler = GetScriptCompiler(!options.NoCache, _logFactory); + var compiler = new ScriptCompiler(_logFactory, !options.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole) { #if NETCOREAPP @@ -31,11 +36,5 @@ public async Task Execute(ExecuteCodeCommandOptions options) }; return await runner.Execute(context); } - - private static ScriptCompiler GetScriptCompiler(bool useRestoreCache, LogFactory logFactory) - { - var compiler = new ScriptCompiler(logFactory, useRestoreCache); - return compiler; - } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommand.cs index 17d4f3cf..a883fce2 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommand.cs @@ -18,7 +18,12 @@ public ExecuteInteractiveCommand(ScriptConsole scriptConsole, LogFactory logFact public async Task Execute(ExecuteInteractiveCommandOptions options) { - var compiler = new ScriptCompiler(_logFactory, useRestoreCache: false); + var compiler = new ScriptCompiler(_logFactory, useRestoreCache: false) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; var runner = new InteractiveRunner(compiler, _logFactory, _scriptConsole, options.PackageSources); if (options.ScriptFile == null) diff --git a/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommandOptions.cs b/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommandOptions.cs index 5b189d06..bb7658ca 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommandOptions.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommandOptions.cs @@ -1,10 +1,12 @@ -using Microsoft.CodeAnalysis; +#if NETCOREAPP +using System.Runtime.Loader; +#endif namespace Dotnet.Script.Core.Commands { public class ExecuteInteractiveCommandOptions { - public ExecuteInteractiveCommandOptions(ScriptFile scriptFile, string[] arguments ,string[] packageSources) + public ExecuteInteractiveCommandOptions(ScriptFile scriptFile, string[] arguments, string[] packageSources) { ScriptFile = scriptFile; Arguments = arguments; @@ -14,5 +16,14 @@ public ExecuteInteractiveCommandOptions(ScriptFile scriptFile, string[] argument public ScriptFile ScriptFile { get; } public string[] Arguments { get; } public string[] PackageSources { get; } + +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs index c406287d..f2a9fd37 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs @@ -25,7 +25,12 @@ public async Task Execute(ExecuteLibraryCommandOptions options } var absoluteFilePath = options.LibraryPath.GetRootedPath(); - var compiler = GetScriptCompiler(!options.NoCache, _logFactory); + var compiler = new ScriptCompiler(_logFactory, !options.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole) { #if NETCOREAPP @@ -35,11 +40,5 @@ public async Task Execute(ExecuteLibraryCommandOptions options var result = await runner.Execute(absoluteFilePath, options.Arguments); return result; } - - private static ScriptCompiler GetScriptCompiler(bool useRestoreCache, LogFactory logFactory) - { - var compiler = new ScriptCompiler(logFactory, useRestoreCache); - return compiler; - } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs index 278ff42c..4b3b176a 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs @@ -64,7 +64,12 @@ private string GetLibrary(ExecuteScriptCommandOptions executeOptions) return pathToLibrary; } - var options = new PublishCommandOptions(executeOptions.File, executionCacheFolder, "script", PublishType.Library, executeOptions.OptimizationLevel, executeOptions.PackageSources, null, executeOptions.NoCache); + var options = new PublishCommandOptions(executeOptions.File, executionCacheFolder, "script", PublishType.Library, executeOptions.OptimizationLevel, executeOptions.PackageSources, null, executeOptions.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = executeOptions.AssemblyLoadContext +#endif + }; new PublishCommand(_scriptConsole, _logFactory).Execute(options); if (hash != null) { diff --git a/src/Dotnet.Script.Core/Commands/PublishCommand.cs b/src/Dotnet.Script.Core/Commands/PublishCommand.cs index 684911c0..7e5311eb 100644 --- a/src/Dotnet.Script.Core/Commands/PublishCommand.cs +++ b/src/Dotnet.Script.Core/Commands/PublishCommand.cs @@ -28,7 +28,12 @@ public void Execute(PublishCommandOptions options) (options.PublishType == PublishType.Library ? Path.Combine(Path.GetDirectoryName(absoluteFilePath), "publish") : Path.Combine(Path.GetDirectoryName(absoluteFilePath), "publish", options.RuntimeIdentifier)); var absolutePublishDirectory = publishDirectory.GetRootedPath(); - var compiler = GetScriptCompiler(!options.NoCache, _logFactory); + var compiler = new ScriptCompiler(_logFactory, !options.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; var scriptEmitter = new ScriptEmitter(_scriptConsole, compiler); var publisher = new ScriptPublisher(_logFactory, scriptEmitter); var code = absoluteFilePath.ToSourceText(); @@ -43,12 +48,5 @@ public void Execute(PublishCommandOptions options) publisher.CreateExecutable(context, _logFactory, options.RuntimeIdentifier, options.LibraryName); } } - - private static ScriptCompiler GetScriptCompiler(bool useRestoreCache, LogFactory logFactory) - { - - var compiler = new ScriptCompiler(logFactory, useRestoreCache); - return compiler; - } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/PublishCommandOptions.cs b/src/Dotnet.Script.Core/Commands/PublishCommandOptions.cs index a82fa295..7dd44e1c 100644 --- a/src/Dotnet.Script.Core/Commands/PublishCommandOptions.cs +++ b/src/Dotnet.Script.Core/Commands/PublishCommandOptions.cs @@ -1,5 +1,8 @@ using Dotnet.Script.DependencyModel.Environment; using Microsoft.CodeAnalysis; +#if NETCOREAPP +using System.Runtime.Loader; +#endif namespace Dotnet.Script.Core.Commands { @@ -25,6 +28,15 @@ public PublishCommandOptions(ScriptFile file, string outputDirectory, string lib public string[] PackageSources { get; } public string RuntimeIdentifier { get; } public bool NoCache { get; } + +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script isolation. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif } public enum PublishType diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index f2749093..fcb3b5a4 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -25,7 +25,7 @@ - + diff --git a/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs new file mode 100644 index 00000000..fcd83e31 --- /dev/null +++ b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs @@ -0,0 +1,140 @@ +#if NETCOREAPP + +using System; +using System.Reflection; +using System.Runtime.Loader; + +#nullable enable + +namespace Dotnet.Script.Core +{ + /// + /// Represents assembly load context for a script with full and automatic assembly isolation. + /// + public class ScriptAssemblyLoadContext : AssemblyLoadContext + { + /// + /// + /// Gets the value indicating whether a specified assembly is homogeneous. + /// + /// + /// Homogeneous assemblies are those shared by both host and scripts. + /// + /// + /// The assembly name. + /// true if the specified assembly is homogeneous; otherwise, false. + protected internal virtual bool IsHomogeneousAssembly(AssemblyName assemblyName) => + assemblyName.Name switch + { + "mscorlib" or + "Microsoft.CodeAnalysis.Scripting" => true, + _ => false + }; + + /// + protected override Assembly? Load(AssemblyName assemblyName) => InvokeLoading(assemblyName); + + /// + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) => InvokeLoadingUnmanagedDll(unmanagedDllName); + + /// + /// Provides data for the event. + /// + public sealed class LoadingEventArgs : EventArgs + { + public LoadingEventArgs(AssemblyName assemblyName) + { + Name = assemblyName; + } + + public AssemblyName Name { get; } + } + + /// + /// Represents a method that handles the event. + /// + /// The sender. + /// The arguments. + /// The loaded assembly or null if the assembly cannot be resolved. + internal delegate Assembly? LoadingEventHandler(ScriptAssemblyLoadContext sender, LoadingEventArgs args); + + LoadingEventHandler? m_Loading; + + /// + /// Occurs when an assembly is being loaded. + /// + internal event LoadingEventHandler Loading + { + add => m_Loading += value; + remove => m_Loading -= value; + } + + Assembly? InvokeLoading(AssemblyName assemblyName) + { + var eh = m_Loading; + if (eh != null) + { + var args = new LoadingEventArgs(assemblyName); + foreach (LoadingEventHandler handler in eh.GetInvocationList()) + { + var assembly = handler(this, args); + if (assembly != null) + return assembly; + } + } + return null; + } + + /// + /// Provides data for the event. + /// + internal sealed class LoadingUnmanagedDllEventArgs : EventArgs + { + public LoadingUnmanagedDllEventArgs(string unmanagedDllName, Func loadUnmanagedDllFromPath) + { + UnmanagedDllName = unmanagedDllName; + LoadUnmanagedDllFromPath = loadUnmanagedDllFromPath; + } + + public string UnmanagedDllName { get; } + public Func LoadUnmanagedDllFromPath { get; } + } + + /// + /// Represents a method that handles the event. + /// + /// The sender. + /// The arguments. + /// The loaded DLL or if the DLL cannot be resolved. + internal delegate IntPtr LoadingUnmanagedDllEventHandler(ScriptAssemblyLoadContext sender, LoadingUnmanagedDllEventArgs args); + + LoadingUnmanagedDllEventHandler? m_LoadingUnmanagedDll; + + /// + /// Occurs when an unmanaged DLL is being loaded. + /// + internal event LoadingUnmanagedDllEventHandler LoadingUnmanagedDll + { + add => m_LoadingUnmanagedDll += value; + remove => m_LoadingUnmanagedDll -= value; + } + + IntPtr InvokeLoadingUnmanagedDll(string unmanagedDllName) + { + var eh = m_LoadingUnmanagedDll; + if (eh != null) + { + var args = new LoadingUnmanagedDllEventArgs(unmanagedDllName, LoadUnmanagedDllFromPath); + foreach (LoadingUnmanagedDllEventHandler handler in eh.GetInvocationList()) + { + var dll = handler(this, args); + if (dll != IntPtr.Zero) + return dll; + } + } + return IntPtr.Zero; + } + } +} + +#endif diff --git a/src/Dotnet.Script.Core/ScriptCompiler.cs b/src/Dotnet.Script.Core/ScriptCompiler.cs index 994cb39f..459f1603 100644 --- a/src/Dotnet.Script.Core/ScriptCompiler.cs +++ b/src/Dotnet.Script.Core/ScriptCompiler.cs @@ -3,6 +3,9 @@ using System.Collections.Immutable; using System.Linq; using System.Reflection; +#if NETCOREAPP +using System.Runtime.Loader; +#endif using System.Text; using System.Threading.Tasks; using Dotnet.Script.Core.Internal; @@ -78,6 +81,15 @@ private ScriptCompiler(LogFactory logFactory, RuntimeDependencyResolver runtimeD } } +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif + public virtual ScriptOptions CreateScriptOptions(ScriptContext context, IList runtimeDependencies) { var scriptMap = runtimeDependencies.ToDictionary(rdt => rdt.Name, rdt => rdt.Scripts); @@ -176,7 +188,19 @@ private ScriptOptions AddScriptReferences(ScriptOptions scriptOptions, Dictionar { foreach (var runtimeAssembly in scriptDependenciesMap.Values) { - loadedAssembliesMap.TryGetValue(runtimeAssembly.Name.Name, out var loadedAssembly); + bool homogenization; +#if NETCOREAPP + homogenization = + AssemblyLoadContext is not ScriptAssemblyLoadContext salc || + salc.IsHomogeneousAssembly(runtimeAssembly.Name); +#else + homogenization = true; +#endif + + Assembly loadedAssembly = null; + if (homogenization) + loadedAssembliesMap.TryGetValue(runtimeAssembly.Name.Name, out loadedAssembly); + if (loadedAssembly == null) { _logger.Trace("Adding reference to a runtime dependency => " + runtimeAssembly); diff --git a/src/Dotnet.Script.Core/ScriptRunner.cs b/src/Dotnet.Script.Core/ScriptRunner.cs index 23d6ba1a..b154d054 100644 --- a/src/Dotnet.Script.Core/ScriptRunner.cs +++ b/src/Dotnet.Script.Core/ScriptRunner.cs @@ -57,15 +57,43 @@ public async Task Execute(string dllPath, IEnumerable #if NETCOREAPP using var assemblyAutoLoader = assemblyLoadContext != null ? new AssemblyAutoLoader(assemblyLoadContext) : null; assemblyAutoLoader?.AddAssembly(assembly); + + Assembly OnLoading(ScriptAssemblyLoadContext sender, ScriptAssemblyLoadContext.LoadingEventArgs args) + { + var assemblyName = args.Name; + + if (sender.IsHomogeneousAssembly(assemblyName)) + { + // The default assembly loader will take care of it. + return null; + } + + return ResolveAssembly(assemblyLoadPal, assemblyName, runtimeDepsMap); + } + + IntPtr OnLoadingUnmanagedDll(ScriptAssemblyLoadContext sender, ScriptAssemblyLoadContext.LoadingUnmanagedDllEventArgs args) + { + string dllPath = assemblyAutoLoader.ResolveUnmanagedDllPath(args.UnmanagedDllName); + if (dllPath == null) + return IntPtr.Zero; + return args.LoadUnmanagedDllFromPath(dllPath); + } + + var scriptAssemblyLoadContext = assemblyLoadContext as ScriptAssemblyLoadContext; + if (scriptAssemblyLoadContext != null) + { + scriptAssemblyLoadContext.Loading += OnLoading; + scriptAssemblyLoadContext.LoadingUnmanagedDll += OnLoadingUnmanagedDll; + } #endif #if NETCOREAPP3_0_OR_GREATER using var contextualReflectionScope = assemblyLoadContext != null ? assemblyLoadContext.EnterContextualReflection() : default; #endif - Assembly OnResolve(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args) => ResolveAssembly(sender, args, runtimeDepsMap); + Assembly OnResolving(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args) => ResolveAssembly(sender, args.Name, runtimeDepsMap); - assemblyLoadPal.Resolving += OnResolve; + assemblyLoadPal.Resolving += OnResolving; try { var type = assembly.GetType("Submission#0"); @@ -78,22 +106,27 @@ public async Task Execute(string dllPath, IEnumerable var submissionStates = new object[2]; submissionStates[0] = globals; - var resultTask = method.Invoke(null, new[] { submissionStates }) as Task; try { - _ = await resultTask; + var resultTask = (Task)method.Invoke(null, new[] { submissionStates }); + return await resultTask; } catch (System.Exception ex) { ScriptConsole.WriteError(ex.ToString()); throw new ScriptRuntimeException("Script execution resulted in an exception.", ex); } - - return await resultTask; } finally { - assemblyLoadPal.Resolving -= OnResolve; + assemblyLoadPal.Resolving -= OnResolving; +#if NETCOREAPP + if (scriptAssemblyLoadContext != null) + { + scriptAssemblyLoadContext.LoadingUnmanagedDll -= OnLoadingUnmanagedDll; + scriptAssemblyLoadContext.Loading -= OnLoading; + } +#endif } } @@ -126,9 +159,9 @@ public virtual async Task Execute(ScriptCompilationCont return ProcessScriptState(scriptResult); } - internal Assembly ResolveAssembly(AssemblyLoadPal pal, AssemblyLoadPal.ResolvingEventArgs args, Dictionary runtimeDepsMap) + internal Assembly ResolveAssembly(AssemblyLoadPal pal, AssemblyName assemblyName, Dictionary runtimeDepsMap) { - var result = runtimeDepsMap.TryGetValue(args.Name.Name, out RuntimeAssembly runtimeAssembly); + var result = runtimeDepsMap.TryGetValue(assemblyName.Name, out RuntimeAssembly runtimeAssembly); if (!result) return null; var loadedAssembly = pal.LoadFrom(runtimeAssembly.Path); return loadedAssembly; diff --git a/src/Dotnet.Script.Tests/ScriptRunnerTests.cs b/src/Dotnet.Script.Tests/ScriptRunnerTests.cs index b97197f5..bebc1aea 100644 --- a/src/Dotnet.Script.Tests/ScriptRunnerTests.cs +++ b/src/Dotnet.Script.Tests/ScriptRunnerTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Reflection; using Dotnet.Script.Core; using Dotnet.Script.DependencyModel.Runtime; using Dotnet.Script.Shared.Tests; @@ -16,7 +17,7 @@ public void ResolveAssembly_ReturnsNull_WhenRuntimeDepsMapDoesNotContainAssembly { var scriptRunner = CreateScriptRunner(); - var result = scriptRunner.ResolveAssembly(AssemblyLoadPal.ForCurrentAppDomain, new AssemblyLoadPal.ResolvingEventArgs(null, "AnyAssemblyName"), new Dictionary()); + var result = scriptRunner.ResolveAssembly(AssemblyLoadPal.ForCurrentAppDomain, new AssemblyName("AnyAssemblyName"), new Dictionary()); Assert.Null(result); } From 056901d990c74a796b075608d787cfab6c2685e2 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Fri, 20 Aug 2021 19:03:45 +0300 Subject: [PATCH 09/18] dotnet-script now uses full assembly isolation for script file execution. --- src/Dotnet.Script/IsolatedAssemblyLoadContext.cs | 10 ---------- src/Dotnet.Script/Program.cs | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 src/Dotnet.Script/IsolatedAssemblyLoadContext.cs diff --git a/src/Dotnet.Script/IsolatedAssemblyLoadContext.cs b/src/Dotnet.Script/IsolatedAssemblyLoadContext.cs deleted file mode 100644 index 67ebe677..00000000 --- a/src/Dotnet.Script/IsolatedAssemblyLoadContext.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Reflection; -using System.Runtime.Loader; - -namespace Dotnet.Script -{ - sealed class IsolatedAssemblyLoadContext : AssemblyLoadContext - { - protected override Assembly Load(AssemblyName assemblyName) => null; - } -} diff --git a/src/Dotnet.Script/Program.cs b/src/Dotnet.Script/Program.cs index 62f3e6a2..bd2fd1d3 100644 --- a/src/Dotnet.Script/Program.cs +++ b/src/Dotnet.Script/Program.cs @@ -247,7 +247,7 @@ private static int Wain(string[] args) nocache.HasValue() ) { - AssemblyLoadContext = new IsolatedAssemblyLoadContext() + AssemblyLoadContext = new ScriptAssemblyLoadContext() }; var fileCommand = new ExecuteScriptCommand(ScriptConsole.Default, logFactory); From 11a3aa3ca5ed2b589fa34d2afeae9ae05226f867 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Fri, 20 Aug 2021 19:04:30 +0300 Subject: [PATCH 10/18] Implemented 'ShouldIsolateScriptAssemblies' test for full assembly isolation. --- src/Dotnet.Script.Tests/ScriptExecutionTests.cs | 6 ++++++ .../TestFixtures/Isolation/Isolation.csx | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 src/Dotnet.Script.Tests/TestFixtures/Isolation/Isolation.csx diff --git a/src/Dotnet.Script.Tests/ScriptExecutionTests.cs b/src/Dotnet.Script.Tests/ScriptExecutionTests.cs index f13fd397..112403b4 100644 --- a/src/Dotnet.Script.Tests/ScriptExecutionTests.cs +++ b/src/Dotnet.Script.Tests/ScriptExecutionTests.cs @@ -472,6 +472,12 @@ public void ShouldIgnoreGlobalJsonInScriptFolder() Assert.Contains("Hello world!", result.output); } + [Fact] + public void ShouldIsolateScriptAssemblies() + { + var result = ScriptTestRunner.Default.ExecuteFixture("Isolation"); + Assert.Contains("10.0.0.0", result.output); + } private static string CreateTestScript(string scriptFolder) { diff --git a/src/Dotnet.Script.Tests/TestFixtures/Isolation/Isolation.csx b/src/Dotnet.Script.Tests/TestFixtures/Isolation/Isolation.csx new file mode 100644 index 00000000..5acb385b --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/Isolation/Isolation.csx @@ -0,0 +1,6 @@ +#r "nuget:Newtonsoft.Json, 10.0.1" + +using Newtonsoft.Json; + +var version = typeof(JsonConvert).Assembly.GetName().Version; +Console.WriteLine(version); From 68c5645ec3cbc69a879187226fdd6dff3d26c765 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Fri, 20 Aug 2021 21:03:31 +0300 Subject: [PATCH 11/18] Fixed library lookup issues with loading of unmanaged DLLs. --- src/Dotnet.Script.Core/Dotnet.Script.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index fcb3b5a4..c8b5af13 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -25,7 +25,7 @@ - + From fad86f6e624ca69740600151b12c1804407d5e73 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Fri, 20 Aug 2021 22:26:54 +0300 Subject: [PATCH 12/18] Enabled assembly isolation for interactive script execution. --- src/Dotnet.Script/Program.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Dotnet.Script/Program.cs b/src/Dotnet.Script/Program.cs index bd2fd1d3..a12cb30b 100644 --- a/src/Dotnet.Script/Program.cs +++ b/src/Dotnet.Script/Program.cs @@ -11,6 +11,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Runtime.Loader; using System.Threading.Tasks; namespace Dotnet.Script @@ -247,7 +248,7 @@ private static int Wain(string[] args) nocache.HasValue() ) { - AssemblyLoadContext = new ScriptAssemblyLoadContext() + AssemblyLoadContext = CreateAssemblyLoadContext() }; var fileCommand = new ExecuteScriptCommand(ScriptConsole.Default, logFactory); @@ -265,16 +266,24 @@ private static int Wain(string[] args) private static async Task RunInteractive(bool useRestoreCache, LogFactory logFactory, string[] packageSources) { - var options = new ExecuteInteractiveCommandOptions(null, Array.Empty(), packageSources); + var options = new ExecuteInteractiveCommandOptions(null, Array.Empty(), packageSources) + { + AssemblyLoadContext = CreateAssemblyLoadContext() + }; await new ExecuteInteractiveCommand(ScriptConsole.Default, logFactory).Execute(options); return 0; } private async static Task RunInteractiveWithSeed(string file, LogFactory logFactory, string[] arguments, string[] packageSources) { - var options = new ExecuteInteractiveCommandOptions(new ScriptFile(file), arguments, packageSources); + var options = new ExecuteInteractiveCommandOptions(new ScriptFile(file), arguments, packageSources) + { + AssemblyLoadContext = CreateAssemblyLoadContext() + }; await new ExecuteInteractiveCommand(ScriptConsole.Default, logFactory).Execute(options); return 0; } + + static AssemblyLoadContext CreateAssemblyLoadContext() => new ScriptAssemblyLoadContext(); } } From 436bbe96584178e489b7588b23885e33ed00f2d9 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Fri, 20 Aug 2021 22:32:27 +0300 Subject: [PATCH 13/18] Inheritance-friendly ScriptAssemblyLoadContext constructors. --- .../ScriptAssemblyLoadContext.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs index fcd83e31..a362e190 100644 --- a/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs +++ b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs @@ -13,6 +13,36 @@ namespace Dotnet.Script.Core /// public class ScriptAssemblyLoadContext : AssemblyLoadContext { + /// + /// Initializes a new instance of the class. + /// + public ScriptAssemblyLoadContext() + { + } + +#if NETCOREAPP3_0_OR_GREATER + /// + /// Initializes a new instance of the class + /// with a name and a value that indicates whether unloading is enabled. + /// + /// + /// + public ScriptAssemblyLoadContext(string? name, bool isCollectible = false) : + base(name, isCollectible) + { + } + + /// + /// Initializes a new instance of the class + /// with a value that indicates whether unloading is enabled. + /// + /// + protected ScriptAssemblyLoadContext(bool isCollectible) : + base(isCollectible) + { + } +#endif + /// /// /// Gets the value indicating whether a specified assembly is homogeneous. From e6aef4713e3ab8910834705f6f348caae5481c0c Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Sun, 22 Aug 2021 13:18:53 +0300 Subject: [PATCH 14/18] Removed a no longer needed workaround for the issue #268. Reference to a more polished version of Gapotchenko.FX.Reflection.Loader. --- src/Dotnet.Script.Core/Dotnet.Script.Core.csproj | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index c8b5af13..b69bf38d 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -25,17 +25,16 @@ - + - - From dcc3a89d396b9929e831ea736f4c3248816b3f41 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Sun, 22 Aug 2021 13:26:36 +0300 Subject: [PATCH 15/18] Removed a no longer needed workaround for the issue #166. --- src/Dotnet.Script.Core/Dotnet.Script.Core.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index b69bf38d..871b5499 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -32,10 +32,6 @@ - - From 6a7c2b36d499d7b4c5d8c9d9978f2fe86743539f Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Sun, 22 Aug 2021 15:32:27 +0300 Subject: [PATCH 16/18] Use a release version of Gapotchenko.FX.Reflection.Loader package. --- src/Dotnet.Script.Core/Dotnet.Script.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index 871b5499..960a4db7 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -25,7 +25,7 @@ - + From 2610c6e93ec2328e4d10b4a2ba02696da2d1b185 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Sun, 22 Aug 2021 16:31:16 +0300 Subject: [PATCH 17/18] Assembly name comparison should be case-insensitive. Fixed internal class visibility. --- .../ScriptAssemblyLoadContext.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs index a362e190..f5691074 100644 --- a/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs +++ b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs @@ -53,13 +53,13 @@ protected ScriptAssemblyLoadContext(bool isCollectible) : /// /// The assembly name. /// true if the specified assembly is homogeneous; otherwise, false. - protected internal virtual bool IsHomogeneousAssembly(AssemblyName assemblyName) => - assemblyName.Name switch - { - "mscorlib" or - "Microsoft.CodeAnalysis.Scripting" => true, - _ => false - }; + protected internal virtual bool IsHomogeneousAssembly(AssemblyName assemblyName) + { + var name = assemblyName.Name; + return + string.Equals(name, "mscorlib", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "Microsoft.CodeAnalysis.Scripting", StringComparison.OrdinalIgnoreCase); + } /// protected override Assembly? Load(AssemblyName assemblyName) => InvokeLoading(assemblyName); @@ -70,7 +70,7 @@ protected internal virtual bool IsHomogeneousAssembly(AssemblyName assemblyName) /// /// Provides data for the event. /// - public sealed class LoadingEventArgs : EventArgs + internal sealed class LoadingEventArgs : EventArgs { public LoadingEventArgs(AssemblyName assemblyName) { From 353609e2a116f1126f75c6da02373a051331de73 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Mon, 23 Aug 2021 20:52:08 +0300 Subject: [PATCH 18/18] Dropped support for .NET Core 2.1. --- src/Dotnet.Script.Core/Dotnet.Script.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index 08426a75..a58748f6 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -4,7 +4,7 @@ A cross platform library allowing you to run C# (CSX) scripts with support for debugging and inline NuGet packages. Based on Roslyn. 1.2.0 filipw - netstandard2.0;netcoreapp2.1;netcoreapp3.1 + netstandard2.0;netcoreapp3.1 Dotnet.Script.Core Dotnet.Script.Core script;csx;csharp;roslyn pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy