From 41528185ec09f24022b026b7b6256ea4d993e1d4 Mon Sep 17 00:00:00 2001 From: Bojan Rajkovic Date: Mon, 21 May 2018 12:58:40 -0400 Subject: [PATCH 1/8] CodeAnalysis: Split EvaluationAssemblyContext for .NET vs .NET Core. On .NET Core, AssemblyLoadContext is a new managed API exposing some of the load context concepts that have existed in the underlying framework for ages. Split the existing EvaluationAssemblyContext into a .NET EAC, which reuses the existing code and hooks the AppDomain's assembly loading events, and a .NET Core version which creates a custom AssemblyLoadContext that hooks the default context to provide assembly loads for evaluation, and hooks unmanaged loading so that we can provide correct paths to native dependencies. --- .../DotNetCoreAgent.cs | 13 +- .../NetCoreEvaluationAssemblyContext.cs | 114 +++++++++++++ .../Xamarin.Interactive.DotNetCore.csproj | 3 + ...AssemblyNameInsensitiveNameOnlyComparer.cs | 39 +++++ .../Evaluating/EvaluationAssemblyContext.cs | 156 ------------------ .../EvaluationAssemblyContextBase.cs | 79 +++++++++ .../Evaluating/EvaluationContext.cs | 8 +- .../Evaluating/EvaluationContextManager.cs | 5 + .../NetFxEvaluationAssemblyContext.cs | 88 ++++++++++ .../CodeAnalysis/WorkspaceConfiguration.cs | 2 +- 10 files changed, 342 insertions(+), 165 deletions(-) create mode 100644 Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs create mode 100644 Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/AssemblyNameInsensitiveNameOnlyComparer.cs delete mode 100644 Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContext.cs create mode 100644 Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContextBase.cs create mode 100644 Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/NetFxEvaluationAssemblyContext.cs diff --git a/Agents/Xamarin.Interactive.DotNetCore/DotNetCoreAgent.cs b/Agents/Xamarin.Interactive.DotNetCore/DotNetCoreAgent.cs index 6e768a9ec..e31da1fad 100644 --- a/Agents/Xamarin.Interactive.DotNetCore/DotNetCoreAgent.cs +++ b/Agents/Xamarin.Interactive.DotNetCore/DotNetCoreAgent.cs @@ -50,11 +50,17 @@ sealed class DotNetCoreEvaluationContextManager : EvaluationContextManager "CS1685" }; + readonly NetCoreEvaluationAssemblyContext evaluationAssemblyContext + = new NetCoreEvaluationAssemblyContext (); + internal DotNetCoreEvaluationContextManager (DotNetCoreAgent agent) : base (agent.RepresentationManager, agent) { } + private protected override EvaluationAssemblyContextBase AssemblyContext + => evaluationAssemblyContext; + protected override object CreateGlobalState () => new EvaluationContextGlobalObject ((DotNetCoreAgent)Context); @@ -76,12 +82,7 @@ internal override void LoadExternalDependencies ( foreach (var externalDep in externalDependencies) { try { Log.Debug (TAG, $"Loading external dependency from {externalDep.Location}…"); - if (MacIntegration.IsMac) { - // Don't do anything for now on Mac, nothing we've tried - // so far works. :( - } else { - WindowsSupport.LoadLibrary (externalDep.Location); - } + evaluationAssemblyContext.Add (externalDep); } catch (Exception e) { Log.Error (TAG, "Could not load external dependency.", e); } diff --git a/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs b/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs new file mode 100644 index 000000000..0d00d41f1 --- /dev/null +++ b/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs @@ -0,0 +1,114 @@ +// +// Author: +// Bojan Rajkovic +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Reflection; +using System.IO; +using System.Runtime.Loader; + +using Xamarin.Interactive.CodeAnalysis.Evaluating; +using Xamarin.Interactive.CodeAnalysis.Resolving; +using Xamarin.Interactive.Logging; + +namespace Xamarin.Interactive.DotNetCore +{ + sealed class NetCoreEvaluationAssemblyContext : EvaluationAssemblyContextBase + { + const string TAG = nameof (NetCoreEvaluationAssemblyContext); + + readonly InteractiveAssemblyLoadContext assemblyLoadContext; + + sealed class InteractiveAssemblyLoadContext : AssemblyLoadContext + { + const string TAG = nameof (InteractiveAssemblyLoadContext); + + NetCoreEvaluationAssemblyContext evaluationAssemblyContext; + + public InteractiveAssemblyLoadContext (NetCoreEvaluationAssemblyContext evaluationAssemblyContext) + => this.evaluationAssemblyContext = evaluationAssemblyContext; + + protected override Assembly Load (AssemblyName assemblyName) + { + Log.Info (TAG, $"Requested assembly load for {assemblyName}."); + + if (evaluationAssemblyContext.NetAssemblyMap.TryGetValue (assemblyName, out var netAssembly)) + return netAssembly; + + if (evaluationAssemblyContext.AssemblyMap.TryGetValue (assemblyName.Name, out var assemblyDefinition)) { + Assembly loadedAsm; + + if (File.Exists (assemblyDefinition.Content.Location)) + loadedAsm = LoadFromAssemblyPath (assemblyDefinition.Content.Location); + else if (assemblyDefinition.Content.PEImage != null) { + var imageStream = new MemoryStream (assemblyDefinition.Content.PEImage); + if (assemblyDefinition.Content.DebugSymbols != null) { + var symStream = new MemoryStream (assemblyDefinition.Content.DebugSymbols); + loadedAsm = LoadFromStream (imageStream, symStream); + } + loadedAsm = LoadFromStream (imageStream); + } else + loadedAsm = null; + + if (loadedAsm == null) { + Log.Warning ( + TAG, + $"Could not load assembly {assemblyName}, location {assemblyDefinition.Content.Location} did not " + + "exist and PEImage was not sent."); + return null; + } + + evaluationAssemblyContext.AssemblyResolvedHandler?.Invoke (loadedAsm, assemblyDefinition); + return loadedAsm; + } + + return null; + } + + protected override IntPtr LoadUnmanagedDll (string unmanagedDllName) + { + Log.Info (TAG, $"Requested unmanaged DLL load for {unmanagedDllName}."); + + // HACK: Try a bunch of names here… + unmanagedDllName = unmanagedDllName + ".dylib"; + + if (!evaluationAssemblyContext.ExternalDependencyMap.ContainsKey (unmanagedDllName)) { + Log.Info (TAG, $"Don't know where to load this from, falling back to base."); + return base.LoadUnmanagedDll (unmanagedDllName); + } + + string unmanagedPath = evaluationAssemblyContext.ExternalDependencyMap [unmanagedDllName]; + Log.Info (TAG, $"Loading unmanaged DLL {unmanagedDllName} from {unmanagedPath}."); + + var unmanagedHandle = LoadUnmanagedDllFromPath (unmanagedPath); + Log.Info (TAG, $"Loaded unmanaged DLL {unmanagedDllName} from {unmanagedPath}, handle is {unmanagedHandle}."); + + return unmanagedHandle; + } + + public IntPtr LoadExternalDependency (AssemblyDependency dependency) + => LoadUnmanagedDllFromPath (dependency.Location.FullPath); + } + + public NetCoreEvaluationAssemblyContext () + { + assemblyLoadContext = new InteractiveAssemblyLoadContext (this); + AssemblyLoadContext.Default.Resolving += HandleAssemblyResolve; + } + + protected override void Dispose (bool disposing) + { + base.Dispose (disposing); + AssemblyLoadContext.Default.Resolving -= HandleAssemblyResolve; + } + + public IntPtr LoadExternalDependency (AssemblyDependency dependency) + => assemblyLoadContext.LoadExternalDependency (dependency); + + Assembly HandleAssemblyResolve (AssemblyLoadContext loadContext, AssemblyName assemblyName) + => assemblyLoadContext.LoadFromAssemblyName (assemblyName); + } +} \ No newline at end of file diff --git a/Agents/Xamarin.Interactive.DotNetCore/Xamarin.Interactive.DotNetCore.csproj b/Agents/Xamarin.Interactive.DotNetCore/Xamarin.Interactive.DotNetCore.csproj index 822379f4c..4f9271c5e 100644 --- a/Agents/Xamarin.Interactive.DotNetCore/Xamarin.Interactive.DotNetCore.csproj +++ b/Agents/Xamarin.Interactive.DotNetCore/Xamarin.Interactive.DotNetCore.csproj @@ -8,4 +8,7 @@ + + + \ No newline at end of file diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/AssemblyNameInsensitiveNameOnlyComparer.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/AssemblyNameInsensitiveNameOnlyComparer.cs new file mode 100644 index 000000000..d8b6cd41b --- /dev/null +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/AssemblyNameInsensitiveNameOnlyComparer.cs @@ -0,0 +1,39 @@ +// +// Author: +// Aaron Bockover +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Xamarin.Interactive.CodeAnalysis.Evaluating +{ + // Implement a custom AssemblyName comparer so that we don't have to insert + // multiple different varieties of the same assembly name into the dictionary. + // Different pieces of external code seem to look up our submission assemblies in + // different ways: JSON.NET uses bare names (see https://bugzilla.xamarin.com/show_bug.cgi?id=58801), + // most of the framework uses fully qualified assembly names, and ASP.NET Core + // seems to use fully-qualified-except-no-version names. As submission assemblies + // aren't versioned, don't have a culture, and don't have a public key token, treating + // the name in a case insensitive way is fine. + sealed class AssemblyNameInsensitiveNameOnlyComparer : IEqualityComparer + { + public static bool Equals (string x, string y) + => string.Equals (x, y, StringComparison.OrdinalIgnoreCase); + + public static bool Equals (AssemblyName x, AssemblyName y) + => Equals (x?.Name, y?.Name); + + public static readonly IEqualityComparer Default + = new AssemblyNameInsensitiveNameOnlyComparer (); + + bool IEqualityComparer.Equals (AssemblyName x, AssemblyName y) + => Equals (x?.Name, y?.Name); + + int IEqualityComparer.GetHashCode (AssemblyName obj) + => obj?.Name == null ? 0 : obj.Name.GetHashCode (); + } +} \ No newline at end of file diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContext.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContext.cs deleted file mode 100644 index 3f211ab85..000000000 --- a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContext.cs +++ /dev/null @@ -1,156 +0,0 @@ -// -// Author: -// Aaron Bockover -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; - -using Xamarin.Interactive.Logging; - -using Xamarin.Interactive.CodeAnalysis.Resolving; - -namespace Xamarin.Interactive.CodeAnalysis.Evaluating -{ - public sealed class EvaluationAssemblyContext : IDisposable - { - // Implement a custom AssemblyName comparer so that we don't have to insert - // multiple different varieties of the same assembly name into the dictionary. - // Different pieces of external code seem to look up our submission assemblies in - // different ways: JSON.NET uses bare names (see https://bugzilla.xamarin.com/show_bug.cgi?id=58801), - // most of the framework uses fully qualified assembly names, and ASP.NET Core - // seems to use fully-qualified-except-no-version names. As submission assemblies - // aren't versioned, don't have a culture, and don't have a public key token, treating - // the name in a case insensitive way is fine. - sealed class AssemblyNameInsensitiveNameOnlyComparer : IEqualityComparer - { - public static bool Equals (string x, string y) - => string.Equals (x, y, StringComparison.OrdinalIgnoreCase); - - public static bool Equals (AssemblyName x, AssemblyName y) - => Equals (x?.Name, y?.Name); - - public static readonly IEqualityComparer Default - = new AssemblyNameInsensitiveNameOnlyComparer (); - - bool IEqualityComparer.Equals (AssemblyName x, AssemblyName y) - => Equals (x?.Name, y?.Name); - - int IEqualityComparer.GetHashCode (AssemblyName obj) - => obj?.Name == null ? 0 : obj.Name.GetHashCode (); - } - - const string TAG = nameof (EvaluationAssemblyContext); - - readonly Dictionary assemblyMap - = new Dictionary ( - StringComparer.OrdinalIgnoreCase); - - readonly Dictionary netAssemblyMap - = new Dictionary ( - AssemblyNameInsensitiveNameOnlyComparer.Default); - - public Action AssemblyResolvedHandler { get; } - - public EvaluationAssemblyContext (Action assemblyResolvedHandler = null) - { - AssemblyResolvedHandler = assemblyResolvedHandler; - AppDomain.CurrentDomain.AssemblyResolve += HandleAssemblyResolve; - } - - public void Dispose () - { - AppDomain.CurrentDomain.AssemblyResolve -= HandleAssemblyResolve; - GC.SuppressFinalize (this); - } - - Assembly HandleAssemblyResolve (object sender, ResolveEventArgs args) - { - Log.Verbose (TAG, $"Handling assembly resolve event for {args.Name}."); - - Assembly netAssembly; - Log.Verbose (TAG, $"Looking up {args.Name} in compiled assembly map."); - if (netAssemblyMap.TryGetValue (new AssemblyName (args.Name), out netAssembly)) - return netAssembly; - - AssemblyDefinition assembly; - var shortName = new AssemblyName (args.Name).Name; - - Log.Verbose (TAG, $"Looking up {shortName} in assembly definition map."); - if (assemblyMap.TryGetValue (shortName, out assembly)) { - if (args.RequestingAssembly?.ReflectionOnly == true) { - if (File.Exists (assembly.Content.Location)) - return Assembly.ReflectionOnlyLoadFrom (assembly.Content.Location); - - if (assembly.Content.PEImage != null) - return Assembly.ReflectionOnlyLoad (assembly.Content.PEImage); - - Log.Verbose ( - TAG, - $"Could not reflection-only load assembly {args.Name}, location {assembly.Content.Location} " + - "did not exist and PEImage was not sent."); - - return null; - } - - Assembly loadedAsm; - - if (File.Exists (assembly.Content.Location)) - loadedAsm = Assembly.LoadFrom (assembly.Content.Location); - else if (assembly.Content.PEImage != null) { - if (assembly.Content.DebugSymbols != null) - loadedAsm = Assembly.Load ( - assembly.Content.PEImage, - assembly.Content.DebugSymbols); - else - loadedAsm = Assembly.Load (assembly.Content.PEImage); - } else - loadedAsm = null; - - if (loadedAsm == null) - Log.Verbose ( - TAG, - $"Could not load assembly {args.Name}, location {assembly.Content.Location} did not " + - "exist and PEImage was not sent."); - - AssemblyResolvedHandler?.Invoke (loadedAsm, assembly); - return loadedAsm; - } - - Log.Verbose (TAG, $"Could not find {args.Name} in any assembly map."); - - return null; - } - - public void AddRange (IEnumerable assemblies) - { - if (assemblies == null) - throw new ArgumentNullException (nameof (assemblies)); - - foreach (var assembly in assemblies) { - if (assembly != null) - Add (assembly); - } - } - - public void Add (AssemblyDefinition assembly) - { - if (assembly == null) - throw new ArgumentNullException (nameof (assembly)); - - assemblyMap [assembly.Name.Name] = assembly; - } - - public void Add (Assembly assembly) - { - if (assembly == null) - throw new ArgumentNullException (nameof (assembly)); - - netAssemblyMap [assembly.GetName ()] = assembly; - } - } -} \ No newline at end of file diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContextBase.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContextBase.cs new file mode 100644 index 000000000..e06cebf19 --- /dev/null +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContextBase.cs @@ -0,0 +1,79 @@ +// +// Author: +// Aaron Bockover +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +using Xamarin.Interactive.Core; +using Xamarin.Interactive.Logging; + +using Xamarin.Interactive.CodeAnalysis.Resolving; + +namespace Xamarin.Interactive.CodeAnalysis.Evaluating +{ + abstract class EvaluationAssemblyContextBase : IDisposable + { + const string TAG = nameof (EvaluationAssemblyContextBase); + + protected Dictionary AssemblyMap { get; } + = new Dictionary ( + StringComparer.OrdinalIgnoreCase); + + protected Dictionary NetAssemblyMap { get; } + = new Dictionary ( + AssemblyNameInsensitiveNameOnlyComparer.Default); + + protected Dictionary ExternalDependencyMap { get; } + = new Dictionary ( + StringComparer.OrdinalIgnoreCase); + + public Action AssemblyResolvedHandler { get; set; } + + public EvaluationAssemblyContextBase (Action assemblyResolvedHandler = null) + => AssemblyResolvedHandler = assemblyResolvedHandler; + + public void Dispose () + => Dispose (true); + + protected virtual void Dispose (bool disposing) + => GC.SuppressFinalize (this); + + public void AddRange (IEnumerable assemblies) + { + if (assemblies == null) + throw new ArgumentNullException (nameof (assemblies)); + + foreach (var assembly in assemblies) { + if (assembly != null) + Add (assembly); + } + } + + public void Add (AssemblyDefinition assembly) + { + if (assembly == null) + throw new ArgumentNullException (nameof (assembly)); + + AssemblyMap [assembly.Name.Name] = assembly; + } + + public void Add (Assembly assembly) + { + if (assembly == null) + throw new ArgumentNullException (nameof (assembly)); + + NetAssemblyMap [assembly.GetName ()] = assembly; + } + + public void Add (AssemblyDependency externalDependency) + { + ExternalDependencyMap [externalDependency.Location.Name] = externalDependency.Location; + } + } +} \ No newline at end of file diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContext.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContext.cs index 60a1df40e..6e28324bd 100644 --- a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContext.cs +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContext.cs @@ -32,16 +32,20 @@ public sealed class EvaluationContext public IObservable Events => events; public EvaluationContextManager Host { get; } - internal EvaluationAssemblyContext AssemblyContext { get; } + internal EvaluationAssemblyContextBase AssemblyContext { get; } internal EvaluationContext ( EvaluationContextManager host, + EvaluationAssemblyContextBase assemblyContext, object globalState) { Host = host ?? throw new ArgumentNullException (nameof (host)); - AssemblyContext = new EvaluationAssemblyContext (HandleAssemblyResolved); + AssemblyContext = assemblyContext + ?? throw new ArgumentNullException (nameof (assemblyContext)); + + AssemblyContext.AssemblyResolvedHandler = HandleAssemblyResolved; submissionStates = new [] { globalState, null }; } diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContextManager.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContextManager.cs index ec00aa4a5..aba4cca9c 100644 --- a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContextManager.cs +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContextManager.cs @@ -69,6 +69,10 @@ readonly Lazy agentSynchronizationContext = new Lazy (); public virtual IAgentSynchronizationContext SynchronizationContexts => agentSynchronizationContext.Value; + readonly Lazy evaluationAssemblyContext + = new Lazy (); + private protected virtual EvaluationAssemblyContextBase AssemblyContext => evaluationAssemblyContext.Value; + readonly IList resetStateHandlers = new List (); public EvaluationContextManager ( @@ -125,6 +129,7 @@ public Task CreateEvaluationContextAsync ( var evaluationContext = new EvaluationContext ( this, + AssemblyContext, globalStateObject); if (globalStateObject is EvaluationContextGlobalObject evaluationContextGlobalObject) diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/NetFxEvaluationAssemblyContext.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/NetFxEvaluationAssemblyContext.cs new file mode 100644 index 000000000..c4f304f80 --- /dev/null +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/NetFxEvaluationAssemblyContext.cs @@ -0,0 +1,88 @@ +// +// Author: +// Aaron Bockover +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Reflection; + +using Xamarin.Interactive.CodeAnalysis.Resolving; +using Xamarin.Interactive.Logging; + +namespace Xamarin.Interactive.CodeAnalysis.Evaluating +{ + sealed class NetFxEvaluationAssemblyContext : EvaluationAssemblyContextBase + { + const string TAG = nameof (NetFxEvaluationAssemblyContext); + + public NetFxEvaluationAssemblyContext () + => AppDomain.CurrentDomain.AssemblyResolve += HandleAssemblyResolve; + + protected override void Dispose (bool disposing) + { + base.Dispose (disposing); + AppDomain.CurrentDomain.AssemblyResolve -= HandleAssemblyResolve; + } + + Assembly HandleAssemblyResolve (object sender, ResolveEventArgs args) + { + Log.Verbose (TAG, $"Handling assembly resolve event for {args.Name}."); + return LoadAssemblyFromName (args.Name, args.RequestingAssembly); + } + + Assembly LoadAssemblyFromName (string assemblyName, Assembly requestingAssembly) + { + Assembly netAssembly; + if (NetAssemblyMap.TryGetValue (new AssemblyName (assemblyName), out netAssembly)) + return netAssembly; + + AssemblyDefinition assembly; + if (AssemblyMap.TryGetValue (new AssemblyName (assemblyName).Name, out assembly)) { + if (requestingAssembly?.ReflectionOnly == true) { + if (File.Exists (assembly.Content.Location)) + return Assembly.ReflectionOnlyLoadFrom (assembly.Content.Location); + + if (assembly.Content.PEImage != null) + return Assembly.ReflectionOnlyLoad (assembly.Content.PEImage); + + Log.Warning ( + TAG, + $"Could not reflection-only load assembly {assemblyName}, location {assembly.Content.Location}" + + "did not exist and PEImage was not sent."); + + return null; + } + + Assembly loadedAsm; + + if (File.Exists (assembly.Content.Location)) + loadedAsm = Assembly.LoadFrom (assembly.Content.Location); + else if (assembly.Content.PEImage != null) { + if (assembly.Content.DebugSymbols != null) + loadedAsm = Assembly.Load ( + assembly.Content.PEImage, + assembly.Content.DebugSymbols); + else + loadedAsm = Assembly.Load (assembly.Content.PEImage); + } else + loadedAsm = null; + + if (loadedAsm == null) { + Log.Warning ( + TAG, + $"Could not load assembly {assemblyName}, location {assembly.Content.Location} did not " + + "exist and PEImage was not sent."); + return null; + } + + AssemblyResolvedHandler?.Invoke (loadedAsm, assembly); + return loadedAsm; + } + + return null; + } + } +} \ No newline at end of file diff --git a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/WorkspaceConfiguration.cs b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/WorkspaceConfiguration.cs index 4a462dd89..80ec7552b 100644 --- a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/WorkspaceConfiguration.cs +++ b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/WorkspaceConfiguration.cs @@ -152,7 +152,7 @@ static Type ResolveHostObjectType ( return typeof (object); } - using (var assemblyContext = new EvaluationAssemblyContext ()) { + using (var assemblyContext = new NetFxEvaluationAssemblyContext ()) { string globalStateAssemblyCachePath = null; if (configuration.GlobalStateType.Assembly.Content.PEImage != null) globalStateAssemblyCachePath = From 464fb46a27c1b1cdb75be656543ff7863ef5a984 Mon Sep 17 00:00:00 2001 From: Bojan Rajkovic Date: Tue, 22 May 2018 12:49:44 -0400 Subject: [PATCH 2/8] CodeAnalysis: Give AssemblyDependency a Name Native dependencies have a Name that can be used to pass the library name that is used for P/Invoke, but this never makes it to agents, because it's stripped in the conversion to AssemblyDependency. Ensure it's preserved so we can use it in the EAC. --- .../EvaluationAssemblyContextBase.cs | 2 +- .../Resolving/AssemblyDependency.cs | 4 ++- .../InteractiveDependencyResolver.cs | 26 +++++++++++-------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContextBase.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContextBase.cs index e06cebf19..5d6b99e93 100644 --- a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContextBase.cs +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContextBase.cs @@ -73,7 +73,7 @@ public void Add (Assembly assembly) public void Add (AssemblyDependency externalDependency) { - ExternalDependencyMap [externalDependency.Location.Name] = externalDependency.Location; + ExternalDependencyMap [externalDependency.Name] = externalDependency.Location; } } } \ No newline at end of file diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Resolving/AssemblyDependency.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Resolving/AssemblyDependency.cs index 74825e32c..55279f88f 100644 --- a/Agents/Xamarin.Interactive/CodeAnalysis/Resolving/AssemblyDependency.cs +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Resolving/AssemblyDependency.cs @@ -12,15 +12,17 @@ namespace Xamarin.Interactive.CodeAnalysis.Resolving [JsonObject] public struct AssemblyDependency { + public string Name { get; } public FilePath Location { get; } public byte [] Data { get; } [JsonConstructor] - internal AssemblyDependency (FilePath location, byte [] data = null) + internal AssemblyDependency (string name, FilePath location, byte [] data = null) { if (location == null) throw new ArgumentNullException (nameof (location)); + Name = name; Location = location; Data = data; } diff --git a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/InteractiveDependencyResolver.cs b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/InteractiveDependencyResolver.cs index 46d2cf4cd..c059c0c61 100644 --- a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/InteractiveDependencyResolver.cs +++ b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/InteractiveDependencyResolver.cs @@ -16,15 +16,9 @@ using System.Reflection.Metadata; using System.Reflection.PortableExecutable; -using Microsoft.CodeAnalysis; - -using Xamarin.Interactive.CodeAnalysis; using Xamarin.Interactive.Core; using Xamarin.Interactive.Logging; -using AssemblyIdentity = Xamarin.Interactive.CodeAnalysis.Resolving.AssemblyIdentity; -using AssemblyDefinition = Xamarin.Interactive.CodeAnalysis.Resolving.AssemblyDefinition; - namespace Xamarin.Interactive.CodeAnalysis.Resolving { public class InteractiveDependencyResolver : NativeDependencyResolver @@ -125,11 +119,7 @@ public Task ResolveReferencesAsync (IEnumerable .Select (r => { var syms = includePeImages ? GetDebugSymbolsFromAssemblyPath (r.Path) : null; var peImage = includePeImages ? GetFileBytes (r.Path) : null; - var externalDeps = r.ExternalDependencies - .Select (d => new AssemblyDependency ( - d.Location, - includePeImages ? GetFileBytes (d.Location) : null)) - .ToArray (); + var externalDeps = GetExternalDependencies (r, includePeImages); return new AssemblyDefinition ( r.AssemblyName, r.Path, @@ -149,6 +139,20 @@ public Task ResolveReferencesAsync (IEnumerable }, cancellationToken); } + static AssemblyDependency [] GetExternalDependencies (ResolvedAssembly resolvedAssembly, bool includePeImages) + { + var externalDependencies = new AssemblyDependency [resolvedAssembly.ExternalDependencies.Length]; + for (var i = 0; i < resolvedAssembly.ExternalDependencies.Length; i++) { + var externalDep = resolvedAssembly.ExternalDependencies [i]; + + externalDependencies [i] = new AssemblyDependency ( + (externalDep is NativeDependency nativeDep) ? nativeDep.Name : null, + externalDep.Location, + includePeImages ? GetFileBytes (externalDep.Location) : null); + } + return externalDependencies; + } + public static byte [] GetFileBytes (FilePath path) { try { From 3c7a95f3e9dc676f0af24ffe947bc53ed8645576 Mon Sep 17 00:00:00 2001 From: Bojan Rajkovic Date: Tue, 22 May 2018 12:50:12 -0400 Subject: [PATCH 3/8] CodeAnalysis: Convert hacked-up paths to DllMaps So much better. --- .../NetCoreEvaluationAssemblyContext.cs | 3 - .../Resolving/NativeDependencyResolver.cs | 216 +++++------------- 2 files changed, 60 insertions(+), 159 deletions(-) diff --git a/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs b/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs index 0d00d41f1..6da6a2bff 100644 --- a/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs +++ b/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs @@ -72,9 +72,6 @@ protected override IntPtr LoadUnmanagedDll (string unmanagedDllName) { Log.Info (TAG, $"Requested unmanaged DLL load for {unmanagedDllName}."); - // HACK: Try a bunch of names here… - unmanagedDllName = unmanagedDllName + ".dylib"; - if (!evaluationAssemblyContext.ExternalDependencyMap.ContainsKey (unmanagedDllName)) { Log.Info (TAG, $"Don't know where to load this from, falling back to base."); return base.LoadUnmanagedDll (unmanagedDllName); diff --git a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs index 3efd449d4..0cdeb3bf1 100644 --- a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs +++ b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs @@ -5,17 +5,21 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; +using NuGet.Packaging; +using NuGet.Versioning; + using Xamarin.Interactive.Core; using Xamarin.Interactive.Logging; +using Xamarin.Interactive.NuGet; namespace Xamarin.Interactive.CodeAnalysis.Resolving { @@ -23,6 +27,32 @@ public class NativeDependencyResolver : DependencyResolver { const string TAG = nameof (NativeDependencyResolver); + const string skiaSharpDllMap = @" + + + + + + + "; + + const string urhoSharpDllMap = @" + + + + + + + "; + + const string libuvDllMap = @" + + + + + + "; + readonly Architecture agentArchitecture; internal TargetCompilationConfiguration CompilationConfiguration { get; } @@ -31,10 +61,8 @@ internal NativeDependencyResolver (TargetCompilationConfiguration compilationCon { CompilationConfiguration = compilationConfiguration; - agentArchitecture = Environment.Is64BitProcess && - (HostEnvironment.OS != HostOS.Windows || compilationConfiguration.Sdk.IsNot (SdkId.ConsoleNetCore)) - ? Architecture.X64 - : Architecture.X86; + // Agents always send an architecture value via the TCC. + agentArchitecture = compilationConfiguration.Runtime.Architecture.Value; } protected override ResolvedAssembly ParseAssembly ( @@ -54,27 +82,38 @@ protected override ResolvedAssembly ParseAssembly ( peReader, metadataReader)); -#if false - - // HACK: Hard-code hacks for SkiaSharp. - if (path.Name == "SkiaSharp.dll") - nativeDependencies.AddRange (GetSkiaSharpDependencies (path)); - // HACK: Hard-code hacks for UrhoSharp. - if (path.Name == "Urho.dll") - nativeDependencies.AddRange (GetUrhoSharpDependencies (path)); - - if (path.Name == "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.dll") - nativeDependencies.AddRange (GetKestrelLibuvDependencies (path)); + var dllMap = new DllMap (CompilationConfiguration.Runtime); + + // Is there a config file next to the assembly? Use that. + // TODO: Check user prefs path or something so that users can write DllMaps + var configPath = path + ".config"; + if (File.Exists (configPath)) + dllMap.Load (configPath); + else { + switch (path.Name) { + case "SkiaSharp.dll": + dllMap.LoadXml (skiaSharpDllMap); + break; + case "UrhoSharp.dll": + dllMap.LoadXml (urhoSharpDllMap); + break; + case "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.dll": + dllMap.LoadXml (GetKestrelDllMap (path)); + break; + } + } -#endif + nativeDependencies.AddRange ( + dllMap.Select (mapping => + new NativeDependency ( + mapping.Key.LibraryName, + path.ParentDirectory.Combine(mapping.Value.LibraryName).FullPath))); return resolvedAssembly.WithExternalDependencies (nativeDependencies); } -#if false - - IEnumerable GetKestrelLibuvDependencies (FilePath path) + string GetKestrelDllMap (FilePath path) { var packageDirectoryPath = path.ParentDirectory.ParentDirectory.ParentDirectory; @@ -98,144 +137,9 @@ IEnumerable GetKestrelLibuvDependencies (FilePath path) .FindBestMatch (libuvVersions); var libuvPackagePath = libuvPackagesPath.Combine (libuvDependencyVersion.ToString ()); - - var architecture = agentArchitecture.ToString ().ToLowerInvariant (); - var isMac = false; - - switch (HostEnvironment.OS) { - case HostOS.Windows: - break; - case HostOS.macOS: - isMac = true; - break; - default: - yield break; - } - - string runtimeName, nativeLibName; - switch (AgentType) { - case AgentType.WPF: - // We need the win7- library here. - nativeLibName = "libuv.dll"; - runtimeName = $"win-{architecture}"; - break; - case AgentType.Console: - case AgentType.MacNet45: - case AgentType.MacMobile: - case AgentType.DotNetCore: - nativeLibName = isMac ? "libuv.dylib" : "libuv.dll"; - runtimeName = isMac ? "osx" : $"win-{architecture}"; - break; - default: - yield break; - } - - var nativeLibraryPath = libuvPackagePath.Combine ( - "runtimes", - runtimeName, - "native", - nativeLibName - ); - - if (nativeLibraryPath.FileExists) - yield return new NativeDependency (nativeLibraryPath.Name, nativeLibraryPath); - } - - IEnumerable GetSkiaSharpDependencies (FilePath path) - { - var packageDirectoryPath = path.ParentDirectory.ParentDirectory.ParentDirectory; - - var architecture = agentArchitecture.ToString ().ToLowerInvariant (); - var isMac = false; - - switch (HostEnvironment.OS) { - case HostOS.Windows: - break; - case HostOS.macOS: - isMac = true; - break; - default: - yield break; - } - - string runtimeName, nativeLibName; - switch (AgentType) { - case AgentType.WPF: - // We need the win7- library here. - nativeLibName = "libSkiaSharp.dll"; - runtimeName = $"win7-{architecture}"; - break; - case AgentType.Console: - case AgentType.MacNet45: - case AgentType.MacMobile: - case AgentType.DotNetCore: - nativeLibName = isMac ? "libSkiaSharp.dylib" : "libSkiaSharp.dll"; - runtimeName = isMac ? "osx" : $"win7-{architecture}"; - break; - default: - yield break; - } - - var nativeLibraryPath = packageDirectoryPath.Combine ( - "runtimes", - runtimeName, - "native", - nativeLibName - ); - - if (nativeLibraryPath.FileExists) - yield return new NativeDependency (nativeLibraryPath.Name, nativeLibraryPath); + return string.Format (libuvDllMap, libuvPackagePath); } - IEnumerable GetUrhoSharpDependencies (FilePath path) - { - var packageDirectoryPath = path.ParentDirectory.ParentDirectory.ParentDirectory; - - var architecture = agentArchitecture == Architecture.X64 ? "64" : "32"; - var isMac = false; - - switch (HostEnvironment.OS) { - case HostOS.Windows: - break; - case HostOS.macOS: - isMac = true; - break; - default: - yield break; - } - - var nativeLibName = isMac ? "libmono-urho.dylib" : "mono-urho.dll"; - - string runtimeName; - switch (AgentType) { - case AgentType.WPF: - runtimeName = $"Win{architecture}"; - break; - case AgentType.MacNet45: - case AgentType.MacMobile: - case AgentType.Console: - case AgentType.DotNetCore: - runtimeName = isMac ? "Mac" : $"Win{architecture}"; - break; - case AgentType.Android: - nativeLibName = "libmono-urho.so"; - yield break; - default: - yield break; - } - - var nativeLibraryPath = packageDirectoryPath.Combine ( - "native", - runtimeName, - nativeLibName - ); - - if (nativeLibraryPath.FileExists) - yield return new NativeDependency (nativeLibraryPath.Name, nativeLibraryPath); - } - -#endif - IEnumerable GetEmbeddedFrameworks ( ResolveOperation resolveOperation, FilePath path, From 105d2263ecedfd9cf062d422d0725f1c9834301d Mon Sep 17 00:00:00 2001 From: Bojan Rajkovic Date: Tue, 22 May 2018 13:23:41 -0400 Subject: [PATCH 4/8] NativeDependencyResolver: Don't crash when a null TCC is passed. --- .../CodeAnalysis/Resolving/NativeDependencyResolver.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs index 0cdeb3bf1..cd30362ba 100644 --- a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs +++ b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs @@ -61,8 +61,13 @@ internal NativeDependencyResolver (TargetCompilationConfiguration compilationCon { CompilationConfiguration = compilationConfiguration; - // Agents always send an architecture value via the TCC. - agentArchitecture = compilationConfiguration.Runtime.Architecture.Value; + if (compilationConfiguration != null) + // Agents always send an architecture value via the TCC. + agentArchitecture = compilationConfiguration.Runtime.Architecture.Value; + else + // This case is reached when we create a resolver with null configuration from + // GacCache, in which case we can stick with the current process architecture. + agentArchitecture = RuntimeInformation.ProcessArchitecture; } protected override ResolvedAssembly ParseAssembly ( From c86e9e9bb980305f5334a6da28eb1337b92b0ecd Mon Sep 17 00:00:00 2001 From: Bojan Rajkovic Date: Tue, 22 May 2018 17:16:47 -0400 Subject: [PATCH 5/8] Logging: Fix LogEntry to be JSON serializable. --- Agents/Xamarin.Interactive/Logging/LogEntry.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Agents/Xamarin.Interactive/Logging/LogEntry.cs b/Agents/Xamarin.Interactive/Logging/LogEntry.cs index d5b87d30f..734db060c 100644 --- a/Agents/Xamarin.Interactive/Logging/LogEntry.cs +++ b/Agents/Xamarin.Interactive/Logging/LogEntry.cs @@ -7,9 +7,11 @@ using System; +using Newtonsoft.Json; + namespace Xamarin.Interactive.Logging { - [Serializable] + [JsonObject] public struct LogEntry { internal string OwnerId { get; } @@ -23,6 +25,7 @@ public struct LogEntry public string CallerFilePath { get; } public int CallerLineNumber { get; } + [JsonConstructor] internal LogEntry ( string ownerId, DateTime time, From 820cae725b74ac56ded1abcf4bee058046934ebe Mon Sep 17 00:00:00 2001 From: Bojan Rajkovic Date: Tue, 22 May 2018 17:23:40 -0400 Subject: [PATCH 6/8] DotNetCore: Fix assembly load cycle with .NET Core EAC. Assembly load cycles are fixed by not calling LoadFromAssemblyName, which ended up just calling base by virtue of not being overridden. Instead, call Load indirectly (can't call it directly as it's protected). --- .../NetCoreEvaluationAssemblyContext.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs b/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs index 6da6a2bff..7636462b0 100644 --- a/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs +++ b/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs @@ -68,6 +68,9 @@ protected override Assembly Load (AssemblyName assemblyName) return null; } + internal Assembly InternalLoadByName (AssemblyName assemblyName) + => Load (assemblyName); + protected override IntPtr LoadUnmanagedDll (string unmanagedDllName) { Log.Info (TAG, $"Requested unmanaged DLL load for {unmanagedDllName}."); @@ -106,6 +109,6 @@ public IntPtr LoadExternalDependency (AssemblyDependency dependency) => assemblyLoadContext.LoadExternalDependency (dependency); Assembly HandleAssemblyResolve (AssemblyLoadContext loadContext, AssemblyName assemblyName) - => assemblyLoadContext.LoadFromAssemblyName (assemblyName); + => assemblyLoadContext.InternalLoadByName (assemblyName); } } \ No newline at end of file From 12dbee20216c4f9db75f2178c042a6da1ca57752 Mon Sep 17 00:00:00 2001 From: Bojan Rajkovic Date: Wed, 23 May 2018 11:24:21 -0400 Subject: [PATCH 7/8] CodeAnalysis: Refactor EAC a bit for more understandable names. Also switch things to use sorted dictionaries for debugging purposes, and ditch the full name binding attempts from earlier. --- .../NetCoreEvaluationAssemblyContext.cs | 60 +++++++++++-------- ...AssemblyNameInsensitiveNameOnlyComparer.cs | 28 ++++++++- .../EvaluationAssemblyContextBase.cs | 18 +++--- .../NetFxEvaluationAssemblyContext.cs | 4 +- 4 files changed, 71 insertions(+), 39 deletions(-) diff --git a/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs b/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs index 7636462b0..c8f96a679 100644 --- a/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs +++ b/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs @@ -35,39 +35,47 @@ protected override Assembly Load (AssemblyName assemblyName) { Log.Info (TAG, $"Requested assembly load for {assemblyName}."); - if (evaluationAssemblyContext.NetAssemblyMap.TryGetValue (assemblyName, out var netAssembly)) + if (evaluationAssemblyContext.CompilationAssemblyMap.TryGetValue (assemblyName, out var netAssembly)) return netAssembly; - if (evaluationAssemblyContext.AssemblyMap.TryGetValue (assemblyName.Name, out var assemblyDefinition)) { - Assembly loadedAsm; - - if (File.Exists (assemblyDefinition.Content.Location)) - loadedAsm = LoadFromAssemblyPath (assemblyDefinition.Content.Location); - else if (assemblyDefinition.Content.PEImage != null) { - var imageStream = new MemoryStream (assemblyDefinition.Content.PEImage); - if (assemblyDefinition.Content.DebugSymbols != null) { - var symStream = new MemoryStream (assemblyDefinition.Content.DebugSymbols); - loadedAsm = LoadFromStream (imageStream, symStream); - } - loadedAsm = LoadFromStream (imageStream); - } else - loadedAsm = null; - - if (loadedAsm == null) { - Log.Warning ( - TAG, - $"Could not load assembly {assemblyName}, location {assemblyDefinition.Content.Location} did not " + - "exist and PEImage was not sent."); - return null; - } + if (evaluationAssemblyContext.ReferencedAssemblyMap.TryGetValue (assemblyName, out var assemblyDefinition)) + return LoadAssemblyFromAssemblyDefinition (assemblyName, assemblyDefinition); - evaluationAssemblyContext.AssemblyResolvedHandler?.Invoke (loadedAsm, assemblyDefinition); - return loadedAsm; - } + Log.Warning ( + TAG, + $"Could not load assembly {assemblyName}, it wasn't present in any list of assemblies."); return null; } + Assembly LoadAssemblyFromAssemblyDefinition (AssemblyName assemblyName, AssemblyDefinition assemblyDefinition) + { + Assembly loadedAsm; + + if (File.Exists (assemblyDefinition.Content.Location)) { + loadedAsm = LoadFromAssemblyPath (assemblyDefinition.Content.Location); + Log.Info (TAG, $"Loaded assembly {loadedAsm} from {assemblyDefinition.Content.Location}."); + } else if (assemblyDefinition.Content.PEImage != null) { + var imageStream = new MemoryStream (assemblyDefinition.Content.PEImage); + if (assemblyDefinition.Content.DebugSymbols != null) { + var symStream = new MemoryStream (assemblyDefinition.Content.DebugSymbols); + loadedAsm = LoadFromStream (imageStream, symStream); + } + loadedAsm = LoadFromStream (imageStream); + Log.Info (TAG, $"Loaded assembly {loadedAsm} from sent PE image."); + } else { + Log.Warning ( + TAG, + $"Could not load assembly {assemblyName}, location {assemblyDefinition.Content.Location} did not " + + "exist and PEImage was not sent."); + return null; + } + + evaluationAssemblyContext.AssemblyResolvedHandler?.Invoke (loadedAsm, assemblyDefinition); + + return loadedAsm; + } + internal Assembly InternalLoadByName (AssemblyName assemblyName) => Load (assemblyName); diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/AssemblyNameInsensitiveNameOnlyComparer.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/AssemblyNameInsensitiveNameOnlyComparer.cs index d8b6cd41b..0d9d9a994 100644 --- a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/AssemblyNameInsensitiveNameOnlyComparer.cs +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/AssemblyNameInsensitiveNameOnlyComparer.cs @@ -19,7 +19,7 @@ namespace Xamarin.Interactive.CodeAnalysis.Evaluating // seems to use fully-qualified-except-no-version names. As submission assemblies // aren't versioned, don't have a culture, and don't have a public key token, treating // the name in a case insensitive way is fine. - sealed class AssemblyNameInsensitiveNameOnlyComparer : IEqualityComparer + sealed class AssemblyNameInsensitiveNameOnlyComparer : IEqualityComparer, IComparer { public static bool Equals (string x, string y) => string.Equals (x, y, StringComparison.OrdinalIgnoreCase); @@ -27,7 +27,7 @@ public static bool Equals (string x, string y) public static bool Equals (AssemblyName x, AssemblyName y) => Equals (x?.Name, y?.Name); - public static readonly IEqualityComparer Default + public static readonly IComparer Default = new AssemblyNameInsensitiveNameOnlyComparer (); bool IEqualityComparer.Equals (AssemblyName x, AssemblyName y) @@ -35,5 +35,29 @@ bool IEqualityComparer.Equals (AssemblyName x, AssemblyName y) int IEqualityComparer.GetHashCode (AssemblyName obj) => obj?.Name == null ? 0 : obj.Name.GetHashCode (); + + public int Compare (AssemblyName x, AssemblyName y) + => string.Compare (x?.Name, y?.Name, StringComparison.OrdinalIgnoreCase); + } + + sealed class AssemblyNameFullNameComparer : IEqualityComparer, IComparer + { + public static bool Equals (string x, string y) + => string.Equals (x, y, StringComparison.OrdinalIgnoreCase); + + public static bool Equals (AssemblyName x, AssemblyName y) + => Equals (x?.Name, y?.Name); + + public static readonly IComparer Default + = new AssemblyNameFullNameComparer (); + + bool IEqualityComparer.Equals (AssemblyName x, AssemblyName y) + => Equals (x?.FullName, y?.FullName); + + int IEqualityComparer.GetHashCode (AssemblyName obj) + => obj?.Name == null ? 0 : obj.Name.GetHashCode (); + + public int Compare (AssemblyName x, AssemblyName y) + => string.Compare (x?.FullName, y?.FullName); } } \ No newline at end of file diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContextBase.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContextBase.cs index 5d6b99e93..708b10ac1 100644 --- a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContextBase.cs +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContextBase.cs @@ -21,16 +21,16 @@ abstract class EvaluationAssemblyContextBase : IDisposable { const string TAG = nameof (EvaluationAssemblyContextBase); - protected Dictionary AssemblyMap { get; } - = new Dictionary ( - StringComparer.OrdinalIgnoreCase); + protected IDictionary ReferencedAssemblyMap { get; } + = new SortedDictionary ( + AssemblyNameInsensitiveNameOnlyComparer.Default); - protected Dictionary NetAssemblyMap { get; } - = new Dictionary ( + protected IDictionary CompilationAssemblyMap { get; } + = new SortedDictionary ( AssemblyNameInsensitiveNameOnlyComparer.Default); - protected Dictionary ExternalDependencyMap { get; } - = new Dictionary ( + protected IDictionary ExternalDependencyMap { get; } + = new SortedDictionary ( StringComparer.OrdinalIgnoreCase); public Action AssemblyResolvedHandler { get; set; } @@ -60,7 +60,7 @@ public void Add (AssemblyDefinition assembly) if (assembly == null) throw new ArgumentNullException (nameof (assembly)); - AssemblyMap [assembly.Name.Name] = assembly; + ReferencedAssemblyMap [assembly.Name] = assembly; } public void Add (Assembly assembly) @@ -68,7 +68,7 @@ public void Add (Assembly assembly) if (assembly == null) throw new ArgumentNullException (nameof (assembly)); - NetAssemblyMap [assembly.GetName ()] = assembly; + CompilationAssemblyMap [assembly.GetName ()] = assembly; } public void Add (AssemblyDependency externalDependency) diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/NetFxEvaluationAssemblyContext.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/NetFxEvaluationAssemblyContext.cs index c4f304f80..d915e9e03 100644 --- a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/NetFxEvaluationAssemblyContext.cs +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/NetFxEvaluationAssemblyContext.cs @@ -36,11 +36,11 @@ Assembly HandleAssemblyResolve (object sender, ResolveEventArgs args) Assembly LoadAssemblyFromName (string assemblyName, Assembly requestingAssembly) { Assembly netAssembly; - if (NetAssemblyMap.TryGetValue (new AssemblyName (assemblyName), out netAssembly)) + if (CompilationAssemblyMap.TryGetValue (new AssemblyName (assemblyName), out netAssembly)) return netAssembly; AssemblyDefinition assembly; - if (AssemblyMap.TryGetValue (new AssemblyName (assemblyName).Name, out assembly)) { + if (ReferencedAssemblyMap.TryGetValue (new AssemblyName (assemblyName), out assembly)) { if (requestingAssembly?.ReflectionOnly == true) { if (File.Exists (assembly.Content.Location)) return Assembly.ReflectionOnlyLoadFrom (assembly.Content.Location); From 836f3214a24c435abe8a96bbb4dcc5c5e9207501 Mon Sep 17 00:00:00 2001 From: Bojan Rajkovic Date: Wed, 23 May 2018 11:14:29 -0400 Subject: [PATCH 8/8] CodeAnalysis: Add a DllMap for Microsoft.ML. --- .../Resolving/NativeDependencyResolver.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs index cd30362ba..c4e77b527 100644 --- a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs +++ b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs @@ -53,6 +53,15 @@ public class NativeDependencyResolver : DependencyResolver "; + const string msmlDllMap = @" + + + + + + + "; + readonly Architecture agentArchitecture; internal TargetCompilationConfiguration CompilationConfiguration { get; } @@ -106,6 +115,10 @@ protected override ResolvedAssembly ParseAssembly ( case "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.dll": dllMap.LoadXml (GetKestrelDllMap (path)); break; + case "Microsoft.ML.CpuMath.dll": + case "Microsoft.ML.FastTree.dll": + dllMap.LoadXml (msmlDllMap); + break; } }