Skip to content

Commit e7002c0

Browse files
committed
Handle assembly loading errors at composition time
The default implementation of MEF's DirectoryCatalog does not handle ReflectionTypeLoadExceptions that occur when assemblies in the specified directory are loaded. This causes the full assembly loading process to stop, meaning that no rules will be found. When running Script Analyzer in a folder with a lot of DLLs, this type of error becomes more likely. This change introduces a new SafeDirectoryCatalog type which is more resilient to this problem. Also, there is a check that determines whether any rules have been loaded before continuing. This check was prone to throwing NullReferenceExceptions when MEF composition fails. I changed that logic to be more resilient to potential null values.
1 parent 30f3d0f commit e7002c0

File tree

3 files changed

+92
-9
lines changed

3 files changed

+92
-9
lines changed

Engine/SafeDirectoryCatalog.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//
2+
// Copyright (c) Microsoft Corporation.
3+
//
4+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10+
// THE SOFTWARE.
11+
//
12+
13+
using System;
14+
using System.ComponentModel.Composition;
15+
using System.ComponentModel.Composition.Hosting;
16+
using System.IO;
17+
using System.Linq;
18+
using System.Reflection;
19+
20+
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer
21+
{
22+
/// <summary>
23+
/// Provides a simple DirectoryCatalog implementation that doesn't
24+
/// fail assembly loading when it encounters a ReflectionTypeLoadException.
25+
/// The default DirectoryCatalog implementation stops evaluating any
26+
/// remaining assemblies in the directory if one of these exceptions
27+
/// are encountered.
28+
/// </summary>
29+
internal class SafeDirectoryCatalog : AggregateCatalog
30+
{
31+
public SafeDirectoryCatalog(string folderLocation, IOutputWriter outputWriter)
32+
{
33+
if (outputWriter == null)
34+
{
35+
throw new ArgumentNullException("outputWriter");
36+
}
37+
38+
// Make sure the directory actually exists
39+
var directoryInfo = new DirectoryInfo(folderLocation);
40+
if (directoryInfo.Exists == false)
41+
{
42+
throw new CompositionException(
43+
"The specified folder does not exist: " + directoryInfo.FullName);
44+
}
45+
46+
// Load each DLL found in the directory
47+
foreach (var dllFile in directoryInfo.GetFileSystemInfos("*.dll"))
48+
{
49+
try
50+
{
51+
// Attempt to create an AssemblyCatalog for this DLL
52+
var assemblyCatalog =
53+
new AssemblyCatalog(
54+
Assembly.LoadFile(
55+
dllFile.FullName));
56+
57+
// We must call ToArray here to pre-initialize the Parts
58+
// IEnumerable and cause it to be stored. The result is
59+
// not used here because it will be accessed later once
60+
// the composition container starts assembling parts.
61+
assemblyCatalog.Parts.ToArray();
62+
63+
this.Catalogs.Add(assemblyCatalog);
64+
}
65+
catch (ReflectionTypeLoadException e)
66+
{
67+
// Write out the exception details and allow the
68+
// loading process to continue
69+
outputWriter.WriteWarning(e.ToString());
70+
}
71+
}
72+
}
73+
}
74+
}

Engine/ScriptAnalyzer.cs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -357,13 +357,16 @@ private void Initialize(
357357

358358
#region Verify rules
359359

360+
// Safely get one non-duplicated list of rules
360361
IEnumerable<IRule> rules =
361-
this.ScriptRules.Union<IRule>(
362-
this.TokenRules).Union<IRule>(
362+
Enumerable.Union<IRule>(
363+
Enumerable.Union<IRule>(
364+
this.ScriptRules ?? Enumerable.Empty<IRule>(),
365+
this.TokenRules ?? Enumerable.Empty<IRule>()),
363366
this.ExternalRules ?? Enumerable.Empty<IExternalRule>());
364367

365368
// Ensure that rules were actually loaded
366-
if (rules == null || rules.Count() == 0)
369+
if (rules == null || rules.Any() == false)
367370
{
368371
this.outputWriter.ThrowTerminatingError(
369372
new ErrorRecord(
@@ -411,22 +414,24 @@ private void LoadRules(Dictionary<string, List<string>> result, CommandInvocatio
411414
{
412415
List<string> paths = new List<string>();
413416

414-
// Clear external rules for each invoke.
415-
ExternalRules = new List<ExternalRule>();
416-
417417
// Initialize helper
418418
Helper.Instance = new Helper(invokeCommand, this.outputWriter);
419419
Helper.Instance.Initialize();
420420

421421
// Clear external rules for each invoke.
422-
ExternalRules = new List<ExternalRule>();
422+
this.ScriptRules = null;
423+
this.TokenRules = null;
424+
this.ExternalRules = null;
423425

424426
// An aggregate catalog that combines multiple catalogs.
425427
using (AggregateCatalog catalog = new AggregateCatalog())
426428
{
427429
// Adds all the parts found in the same directory.
428430
string dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
429-
catalog.Catalogs.Add(new DirectoryCatalog(dirName));
431+
catalog.Catalogs.Add(
432+
new SafeDirectoryCatalog(
433+
dirName,
434+
this.outputWriter));
430435

431436
// Adds user specified directory
432437
paths = result.ContainsKey("ValidDllPaths") ? result["ValidDllPaths"] : result["ValidPaths"];
@@ -438,7 +443,10 @@ private void LoadRules(Dictionary<string, List<string>> result, CommandInvocatio
438443
}
439444
else
440445
{
441-
catalog.Catalogs.Add(new DirectoryCatalog(path));
446+
catalog.Catalogs.Add(
447+
new SafeDirectoryCatalog(
448+
path,
449+
this.outputWriter));
442450
}
443451
}
444452

Engine/ScriptAnalyzerEngine.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
<Compile Include="Helper.cs" />
7272
<Compile Include="IOutputWriter.cs" />
7373
<Compile Include="Loggers\WriteObjectsLogger.cs" />
74+
<Compile Include="SafeDirectoryCatalog.cs" />
7475
<Compile Include="ScriptAnalyzer.cs" />
7576
<Compile Include="SpecialVars.cs" />
7677
<Compile Include="Strings.Designer.cs">

0 commit comments

Comments
 (0)