Skip to content

Commit 3856c76

Browse files
Fix 'Failed to resolve assembly' cecil issue (#625)
Fix 'Failed to resolve assembly' cecil issue
1 parent 96984ee commit 3856c76

File tree

7 files changed

+328
-93
lines changed

7 files changed

+328
-93
lines changed

Directory.Build.targets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<PackageReference Update="McMaster.Extensions.CommandLineUtils" Version="2.3.4" />
44
<PackageReference Update="Microsoft.Build.Utilities.Core" Version="15.5.180"/>
55
<PackageReference Update="Microsoft.CodeAnalysis.CSharp" Version="2.10.0" />
6+
<PackageReference Update="Microsoft.Extensions.DependencyModel" Version="2.1.0" />
67
<PackageReference Update="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
78
<PackageReference Update="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
89
<PackageReference Update="Microsoft.Extensions.FileSystemGlobbing" Version="2.0.1" />

src/coverlet.core/Exceptions.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
3+
namespace Coverlet.Core.Exceptions
4+
{
5+
[Serializable]
6+
internal class CoverletException : Exception
7+
{
8+
public CoverletException() { }
9+
public CoverletException(string message) : base(message) { }
10+
public CoverletException(string message, System.Exception inner) : base(message, inner) { }
11+
protected CoverletException(
12+
System.Runtime.Serialization.SerializationInfo info,
13+
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
14+
}
15+
16+
[Serializable]
17+
internal class CecilAssemblyResolutionException : CoverletException
18+
{
19+
public CecilAssemblyResolutionException() { }
20+
public CecilAssemblyResolutionException(string message) : base(message) { }
21+
public CecilAssemblyResolutionException(string message, System.Exception inner) : base(message, inner) { }
22+
protected CecilAssemblyResolutionException(
23+
System.Runtime.Serialization.SerializationInfo info,
24+
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
25+
}
26+
}
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using Coverlet.Core.Abstracts;
7+
using Coverlet.Core.Exceptions;
8+
using Microsoft.Extensions.DependencyModel;
9+
using Microsoft.Extensions.DependencyModel.Resolution;
10+
using Mono.Cecil;
11+
12+
namespace Coverlet.Core.Instrumentation
13+
{
14+
/// <summary>
15+
/// In case of testing different runtime i.e. netfx we could find netstandard.dll in folder.
16+
/// netstandard.dll is a forward only lib, there is no IL but only forwards to "runtime" implementation.
17+
/// For some classes implementation are in different assembly for different runtime for instance:
18+
///
19+
/// For NetFx 4.7
20+
/// // Token: 0x2700072C RID: 1836
21+
/// .class extern forwarder System.Security.Cryptography.X509Certificates.StoreName
22+
/// {
23+
/// .assembly extern System
24+
/// }
25+
///
26+
/// For netcoreapp2.2
27+
/// Token: 0x2700072C RID: 1836
28+
/// .class extern forwarder System.Security.Cryptography.X509Certificates.StoreName
29+
/// {
30+
/// .assembly extern System.Security.Cryptography.X509Certificates
31+
/// }
32+
///
33+
/// There is a concrete possibility that Cecil cannot find implementation and throws StackOverflow exception https://github.com/jbevain/cecil/issues/575
34+
/// This custom resolver check if requested lib is a "official" netstandard.dll and load once of "current runtime" with
35+
/// correct forwards.
36+
/// Check compares 'assembly name' and 'public key token', because versions could differ between runtimes.
37+
/// </summary>
38+
internal class NetstandardAwareAssemblyResolver : DefaultAssemblyResolver
39+
{
40+
private static readonly System.Reflection.Assembly _netStandardAssembly;
41+
private static readonly string _name;
42+
private static readonly byte[] _publicKeyToken;
43+
private static readonly AssemblyDefinition _assemblyDefinition;
44+
45+
private readonly string _modulePath;
46+
private readonly Lazy<CompositeCompilationAssemblyResolver> _compositeResolver;
47+
private readonly ILogger _logger;
48+
49+
static NetstandardAwareAssemblyResolver()
50+
{
51+
try
52+
{
53+
// To be sure to load information of "real" runtime netstandard implementation
54+
_netStandardAssembly = System.Reflection.Assembly.LoadFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "netstandard.dll"));
55+
System.Reflection.AssemblyName name = _netStandardAssembly.GetName();
56+
_name = name.Name;
57+
_publicKeyToken = name.GetPublicKeyToken();
58+
_assemblyDefinition = AssemblyDefinition.ReadAssembly(_netStandardAssembly.Location);
59+
}
60+
catch (FileNotFoundException)
61+
{
62+
// netstandard not supported
63+
}
64+
}
65+
66+
public NetstandardAwareAssemblyResolver(string modulePath, ILogger logger)
67+
{
68+
_modulePath = modulePath;
69+
_logger = logger;
70+
71+
// this is lazy because we cannot create AspNetCoreSharedFrameworkResolver if not on .NET Core runtime,
72+
// runtime folders are different
73+
_compositeResolver = new Lazy<CompositeCompilationAssemblyResolver>(() => new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[]
74+
{
75+
new AppBaseCompilationAssemblyResolver(),
76+
new ReferenceAssemblyPathResolver(),
77+
new PackageCompilationAssemblyResolver(),
78+
new AspNetCoreSharedFrameworkResolver(_logger)
79+
}), true);
80+
}
81+
82+
// Check name and public key but not version that could be different
83+
private bool CheckIfSearchingNetstandard(AssemblyNameReference name)
84+
{
85+
if (_netStandardAssembly is null)
86+
{
87+
return false;
88+
}
89+
90+
if (_name != name.Name)
91+
{
92+
return false;
93+
}
94+
95+
if (name.PublicKeyToken.Length != _publicKeyToken.Length)
96+
{
97+
return false;
98+
}
99+
100+
for (int i = 0; i < name.PublicKeyToken.Length; i++)
101+
{
102+
if (_publicKeyToken[i] != name.PublicKeyToken[i])
103+
{
104+
return false;
105+
}
106+
}
107+
108+
return true;
109+
}
110+
111+
public override AssemblyDefinition Resolve(AssemblyNameReference name)
112+
{
113+
if (CheckIfSearchingNetstandard(name))
114+
{
115+
return _assemblyDefinition;
116+
}
117+
else
118+
{
119+
try
120+
{
121+
return base.Resolve(name);
122+
}
123+
catch (AssemblyResolutionException)
124+
{
125+
AssemblyDefinition asm = TryWithCustomResolverOnDotNetCore(name);
126+
127+
if (asm != null)
128+
{
129+
return asm;
130+
}
131+
132+
throw;
133+
}
134+
}
135+
}
136+
137+
private bool IsDotNetCore()
138+
{
139+
// object for .NET Framework is inside mscorlib.dll
140+
return Path.GetFileName(typeof(object).Assembly.Location) == "System.Private.CoreLib.dll";
141+
}
142+
143+
/// <summary>
144+
///
145+
/// We try to manually load assembly.
146+
/// To work test project needs to use
147+
///
148+
/// <PropertyGroup>
149+
/// <PreserveCompilationContext>true</PreserveCompilationContext>
150+
/// </PropertyGroup>
151+
///
152+
/// </summary>
153+
internal AssemblyDefinition TryWithCustomResolverOnDotNetCore(AssemblyNameReference name)
154+
{
155+
_logger.LogVerbose($"TryWithCustomResolverOnDotNetCore for {name}");
156+
157+
if (!IsDotNetCore())
158+
{
159+
_logger.LogVerbose($"Not a dotnet core app");
160+
return null;
161+
}
162+
163+
if (string.IsNullOrEmpty(_modulePath))
164+
{
165+
throw new AssemblyResolutionException(name);
166+
}
167+
168+
using DependencyContextJsonReader contextJsonReader = new DependencyContextJsonReader();
169+
Dictionary<string, Lazy<AssemblyDefinition>> libraries = new Dictionary<string, Lazy<AssemblyDefinition>>();
170+
foreach (string fileName in Directory.GetFiles(Path.GetDirectoryName(_modulePath), "*.deps.json"))
171+
{
172+
using FileStream depsFile = File.OpenRead(fileName);
173+
_logger.LogVerbose($"Loading {fileName}");
174+
DependencyContext dependencyContext = contextJsonReader.Read(depsFile);
175+
foreach (CompilationLibrary library in dependencyContext.CompileLibraries)
176+
{
177+
// we're interested only on nuget package
178+
if (library.Type == "project")
179+
{
180+
continue;
181+
}
182+
183+
try
184+
{
185+
string path = library.ResolveReferencePaths(_compositeResolver.Value).FirstOrDefault();
186+
if (string.IsNullOrEmpty(path))
187+
{
188+
continue;
189+
}
190+
191+
// We could load more than one deps file, we need to check if lib is already found
192+
if (!libraries.ContainsKey(library.Name))
193+
{
194+
libraries.Add(library.Name, new Lazy<AssemblyDefinition>(() => AssemblyDefinition.ReadAssembly(path, new ReaderParameters() { AssemblyResolver = this })));
195+
}
196+
}
197+
catch (Exception ex)
198+
{
199+
// if we don't find a lib go on
200+
_logger.LogVerbose($"TryWithCustomResolverOnDotNetCore exception: {ex.ToString()}");
201+
}
202+
}
203+
}
204+
205+
if (libraries.TryGetValue(name.Name, out Lazy<AssemblyDefinition> asm))
206+
{
207+
return asm.Value;
208+
}
209+
210+
throw new CecilAssemblyResolutionException($"AssemblyResolutionException for '{name}'. Try to add <PreserveCompilationContext>true</PreserveCompilationContext> to test projects </PropertyGroup> or pass '/p:CopyLocalLockFileAssemblies=true' option to the 'dotnet test' command-line", new AssemblyResolutionException(name));
211+
}
212+
}
213+
214+
internal class AspNetCoreSharedFrameworkResolver : ICompilationAssemblyResolver
215+
{
216+
private readonly string[] _aspNetSharedFrameworkDirs = null;
217+
private readonly ILogger _logger = null;
218+
219+
public AspNetCoreSharedFrameworkResolver(ILogger logger)
220+
{
221+
_logger = logger;
222+
string runtimeRootPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
223+
string runtimeVersion = runtimeRootPath.Substring(runtimeRootPath.LastIndexOf(Path.DirectorySeparatorChar) + 1);
224+
_aspNetSharedFrameworkDirs = new string[]
225+
{
226+
Path.GetFullPath(Path.Combine(runtimeRootPath,"../../Microsoft.AspNetCore.All", runtimeVersion)),
227+
Path.GetFullPath(Path.Combine(runtimeRootPath, "../../Microsoft.AspNetCore.App", runtimeVersion))
228+
};
229+
230+
_logger.LogVerbose("AspNetCoreSharedFrameworkResolver search paths:");
231+
foreach (string searchPath in _aspNetSharedFrameworkDirs)
232+
{
233+
_logger.LogVerbose(searchPath);
234+
}
235+
}
236+
237+
public bool TryResolveAssemblyPaths(CompilationLibrary library, List<string> assemblies)
238+
{
239+
string dllName = $"{library.Name}.dll";
240+
241+
foreach (string sharedFrameworkPath in _aspNetSharedFrameworkDirs)
242+
{
243+
if (!Directory.Exists(sharedFrameworkPath))
244+
{
245+
continue;
246+
}
247+
248+
foreach (var file in Directory.GetFiles(sharedFrameworkPath))
249+
{
250+
if (Path.GetFileName(file).Equals(dllName, StringComparison.OrdinalIgnoreCase))
251+
{
252+
_logger.LogVerbose($"'{dllName}' found in '{file}'");
253+
assemblies.Add(file);
254+
return true;
255+
}
256+
}
257+
}
258+
259+
return false;
260+
}
261+
}
262+
}

0 commit comments

Comments
 (0)