Skip to content

Commit

Permalink
- #355: Error in Eval using a referenced assembly with unloading enab…
Browse files Browse the repository at this point in the history
…led.
  • Loading branch information
oleg-shilo committed Dec 6, 2023
1 parent 72837e1 commit af55aab
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 26 deletions.
1 change: 0 additions & 1 deletion src/BuildServer/out/build.runtimeconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"version": "8.0.0"
},
"configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
Expand Down
69 changes: 52 additions & 17 deletions src/CSScriptLib/src/CSScriptLib/Evaluator.Roslyn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,6 @@

#endregion License...

using csscript;
using CSScripting;
using CSScripting.CodeDom;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Emit;

//using Microsoft.CodeAnalysis;
//using Microsoft.CodeAnalysis.CSharp.Scripting
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Diagnostics;
Expand All @@ -54,6 +41,19 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Emit;

//using Microsoft.CodeAnalysis;
//using Microsoft.CodeAnalysis.CSharp.Scripting
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.Text;
using csscript;
using CSScripting;
using CSScripting.CodeDom;

// <summary>
//<package id="Microsoft.Net.Compilers" version="1.2.0-beta-20151211-01" targetFramework="net45" developmentDependency="true" />
Expand Down Expand Up @@ -134,6 +134,17 @@ public string AssemblyFile
/// </value>
public string AssemblyName { set; get; }

/// <summary>
/// Gets or sets the loaded script assembly.
/// <para> This member is set to the script assembly loaded in the calling ppDomain.
/// It is particularly useful when you need access the script assembly (e.g. to unload it)
/// but it is unavailable to the caller because the evaluation expression does not return any result (`void`).</para>
/// </summary>
/// <value>
/// The loaded assembly.
/// </value>
public Assembly LoadedAssembly { set; get; }

/// <summary>
/// Gets or sets a value indicating whether to prefer loading compiled script from the
/// assembly file when it is available.
Expand Down Expand Up @@ -498,10 +509,10 @@ void add_code(string file, string[] codeLines, int lineOffset)
// add references from code and host-specified

compilation = CSharpCompilation.CreateScriptCompilation(
assemblyName: "Script" + Guid.NewGuid(),
syntaxTree,
references,
returnType: typeof(object));
assemblyName: "Script" + Guid.NewGuid(),
syntaxTree,
references,
returnType: typeof(object));

var entryPoint = compilation.GetEntryPoint(CancellationToken.None);
info.ScriptEntryPoint = entryPoint.MetadataName;
Expand Down Expand Up @@ -703,6 +714,30 @@ public override IEvaluator ReferenceAssembly(Assembly assembly)
return instance;
}

public new T Eval<T>(string scriptText, CompileInfo info)
{
// triggered by #355: Error in Eval using a referenced assembly with unloading enabled.

if (this.GetType() != typeof(RoslynEvaluator))
throw new Exception("This method is only available for Roslyn evaluator.");

if (info.CodeKind != SourceCodeKind.Script)
throw new Exception($"The parameter `{nameof(info)}.{nameof(info.CodeKind)}` must be set to `{nameof(SourceCodeKind.Script)}`.");

var asm = CompileCode(scriptText, info);
info.LoadedAssembly = asm;

var entryPointType = asm.GetType($".{info.RootClass}", true, false);
var entryPointMethod = entryPointType?.GetTypeInfo().GetDeclaredMethod(info.ScriptEntryPoint) ?? throw new InvalidOperationException("Script entry point method could be found.");

// var allTypes = asm.GetTypes(); // [1] is our method. Will be needed if SourceCodeKind.Script support is extended to "LoadCode" API

var submissionFactory = (Func<object[], Task<object>>)entryPointMethod.CreateDelegate(typeof(Func<object[], Task<object>>));
dynamic instance = submissionFactory.Invoke(new object[] { null, null }).Result;

return instance;
}

List<Assembly> refAssemblies = new List<Assembly>();

IEvaluator PrepareRefAssemblies()
Expand Down
39 changes: 35 additions & 4 deletions src/CSScriptLib/src/Client.NET-Core/Program.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using CSScripting;
using CSScriptLib;
using System;
using System;
using System.Diagnostics;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Reflection;
using CSScripting;
using CSScriptLib;

namespace ConsoleApp1
{
class Program
public class Program
{
static void Main(string[] args)
{
Expand Down Expand Up @@ -78,6 +79,33 @@ static void call_UnloadAssembly()
script.GetType().Assembly.Unload();
}

static Assembly printer_asm;

static void call_UnloadAssemblyWithDependency()
{
var info = new CompileInfo { RootClass = "Printing", AssemblyFile = "Printer.dll", AssemblyName = "PrintAsm" };

if (printer_asm == null)
printer_asm = CSScript.Evaluator
.CompileCode(@"using System;
public class Printer
{
public static void Print() =>
Console.WriteLine(""Printing..."");
}", info);

var script = CSScript.Evaluator
.With(eval => eval.IsAssemblyUnloadingEnabled = true)
.ReferenceAssembly(printer_asm)
.LoadMethod<ICalc>(@"public int Sum(int a, int b)
{
Printing.Printer.Print();
return a+b;
}");
script.Sum(1, 2);
script.GetType().Assembly.Unload();
}

static void call_UnloadAssembly_Failing()
{
// using 'dynamic` completely breaks CLR unloading mechanism. Most likely it triggers an
Expand Down Expand Up @@ -114,7 +142,10 @@ static void Test_Unloading()
for (int i = 0; i < 20; i++)
{
Console.WriteLine("Loaded assemblies count: " + AppDomain.CurrentDomain.GetAssemblies().Count());

call_UnloadAssembly();
//call_UnloadAssemblyWithDependency(); // also works OK; provided just for demo

// call_UnloadAssembly_Failing();
// call_UnloadAssembly_Crashing_CLR();
GC.Collect();
Expand Down
36 changes: 32 additions & 4 deletions src/Tests.CSScriptLib/Evaluator.Roslyn.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,10 @@ public void Issue_337()
var accounting_assm2 = CSScript.Evaluator
.With(e => e.IsCachingEnabled = false)
.CompileCode(@"public class TXBase
{
public string TranId { get; set; }
public string HashKey { get; set; }
}", info);
{
public string TranId { get; set; }
public string HashKey { get; set; }
}", info);

dynamic script1 = CSScript.Evaluator
.ReferenceAssembly(accounting_assm2)
Expand All @@ -336,6 +336,34 @@ public string Test()
// Assert.Equal(3, statements.Count());
}

public static string log = "";

[Fact]
public void Issue_354()
{
var info = new CompileInfo { RootClass = "Printing", AssemblyFile = "Printer.dll" };

var printer_asm = CSScript.Evaluator
.ReferenceAssemblyOf(this)
.CompileCode(@"using System;
using System.Diagnostics;
public class Printer
{
public static void Print()
{
EvaluatorTests.Generic_Roslyn.log = ""test"";
Debug.WriteLine(""Printing..."");
}
}", info);

dynamic script = CSScript.Evaluator
.ReferenceAssembly(printer_asm)
.Eval("Printing.Printer.Print();");
// .LoadMethod(@"public void TestPrint() => Printing.Printer.Print();");

// Assert.Equal(3, statements.Count());
}

[Fact]
public void Issue_297()
{
Expand Down

0 comments on commit af55aab

Please sign in to comment.