From 27b408b648db7ae90e696b4a50b87ebd425d03b3 Mon Sep 17 00:00:00 2001 From: markcowl Date: Fri, 29 Jan 2016 19:01:49 -0800 Subject: [PATCH 1/6] Adding static analysis tool with dependency checker rules --- tools/StaticAnalysis/AnalysisLogger.cs | 101 ++++++ tools/StaticAnalysis/ConsoleLogger.cs | 51 +++ tools/StaticAnalysis/Decorator.cs | 74 +++++ .../DependencyAnalyzer/AssemblyLoader.cs | 86 +++++ .../DependencyAnalyzer/AssemblyMetadata.cs | 67 ++++ .../AssemblyNameComparer.cs | 38 +++ .../DependencyAnalyzer/AssemblyRecord.cs | 143 ++++++++ .../AssemblyVersionConflict.cs | 80 +++++ .../DependencyAnalyzer/DependencyAnalyzer.cs | 310 ++++++++++++++++++ .../DependencyAnalyzer/ExtraAssembly.cs | 48 +++ .../DependencyAnalyzer/MissingAssembly.cs | 46 +++ .../SharedAssemblyConflict.cs | 41 +++ tools/StaticAnalysis/IReportRecord.cs | 19 ++ tools/StaticAnalysis/IStaticAnalyzer.cs | 41 +++ tools/StaticAnalysis/Program.cs | 57 ++++ .../StaticAnalysis/Properties/AssemblyInfo.cs | 36 ++ tools/StaticAnalysis/ReportLogger.cs | 83 +++++ tools/StaticAnalysis/Static Analysis.csproj | 70 ++++ tools/StaticAnalysis/Static Analysis.sln | 22 ++ 19 files changed, 1413 insertions(+) create mode 100644 tools/StaticAnalysis/AnalysisLogger.cs create mode 100644 tools/StaticAnalysis/ConsoleLogger.cs create mode 100644 tools/StaticAnalysis/Decorator.cs create mode 100644 tools/StaticAnalysis/DependencyAnalyzer/AssemblyLoader.cs create mode 100644 tools/StaticAnalysis/DependencyAnalyzer/AssemblyMetadata.cs create mode 100644 tools/StaticAnalysis/DependencyAnalyzer/AssemblyNameComparer.cs create mode 100644 tools/StaticAnalysis/DependencyAnalyzer/AssemblyRecord.cs create mode 100644 tools/StaticAnalysis/DependencyAnalyzer/AssemblyVersionConflict.cs create mode 100644 tools/StaticAnalysis/DependencyAnalyzer/DependencyAnalyzer.cs create mode 100644 tools/StaticAnalysis/DependencyAnalyzer/ExtraAssembly.cs create mode 100644 tools/StaticAnalysis/DependencyAnalyzer/MissingAssembly.cs create mode 100644 tools/StaticAnalysis/DependencyAnalyzer/SharedAssemblyConflict.cs create mode 100644 tools/StaticAnalysis/IReportRecord.cs create mode 100644 tools/StaticAnalysis/IStaticAnalyzer.cs create mode 100644 tools/StaticAnalysis/Program.cs create mode 100644 tools/StaticAnalysis/Properties/AssemblyInfo.cs create mode 100644 tools/StaticAnalysis/ReportLogger.cs create mode 100644 tools/StaticAnalysis/Static Analysis.csproj create mode 100644 tools/StaticAnalysis/Static Analysis.sln diff --git a/tools/StaticAnalysis/AnalysisLogger.cs b/tools/StaticAnalysis/AnalysisLogger.cs new file mode 100644 index 000000000000..abff4f02131a --- /dev/null +++ b/tools/StaticAnalysis/AnalysisLogger.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StaticAnalysis +{ + /// + /// Abstract class to implement the logging structure + /// + public abstract class AnalysisLogger + { + string _baseDirectory; + + /// + /// Create an analysis logger that will write reports to the given directory + /// + /// + public AnalysisLogger(string baseDirectory) + { + _baseDirectory = baseDirectory; + } + + IList _loggers = new List(); + protected virtual IList Loggers { get { return _loggers; } } + + /// + /// Write an error report. + /// + /// The message to write + public abstract void WriteError(string error); + + public virtual void WriteError(string format, params object[] args) + { + WriteError(string.Format(format, args)); + } + + /// + /// Write an informational message. + /// + /// The message to write + public abstract void WriteMessage(string message); + + public virtual void WriteMessage(string format, params object[] args) + { + WriteMessage(string.Format(format, args)); + } + + /// + /// Write a warning. + /// + /// The warning text + public abstract void WriteWarning(string message); + + public virtual void WriteWarning(string format, params object[] args) + { + WriteWarning(string.Format(format, args)); + } + + /// + /// Write a report file to the given file, using the given file contents. + /// + /// The path to the file + /// The contents of the report + public abstract void WriteReport(string name, string contents); + + /// + /// Create a logger for a particular report + /// + /// The type of records written to the log + /// The filename (without file path) where the report will be written + /// The given logger. Analyzer may write records to this logger and they will be written to the report file. + public virtual ReportLogger CreateLogger(string fileName) where T: IReportRecord, new() + { + var filePath = Path.Combine(_baseDirectory, fileName); + var logger = new ReportLogger(filePath, this); + Loggers.Add(logger); + return logger; + } + + /// + /// Write out the report files for each of the added report loggers. + /// + public void WriteReports() + { + foreach (var logger in Loggers.Where(l => l.Records.Any())) + { + StringBuilder reportText = new StringBuilder(); + reportText.AppendLine(logger.Records.First().PrintHeaders()); + foreach (var reportRecord in logger.Records) + { + reportText.AppendLine(reportRecord.FormatRecord()); + } + + WriteReport(logger.FileName, reportText.ToString()); + } + } + } +} diff --git a/tools/StaticAnalysis/ConsoleLogger.cs b/tools/StaticAnalysis/ConsoleLogger.cs new file mode 100644 index 000000000000..4fa3180f11a7 --- /dev/null +++ b/tools/StaticAnalysis/ConsoleLogger.cs @@ -0,0 +1,51 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.IO; + +namespace StaticAnalysis +{ + /// + /// Simple class for logging information + /// + public class ConsoleLogger : AnalysisLogger + { + + public ConsoleLogger(string baseDirectory) : base(baseDirectory) + { + } + + public override void WriteError(string error) + { + Console.WriteLine(string.Format("### ERROR {0}", error)); + } + + public override void WriteMessage(string message) + { + Console.WriteLine(message); + } + + public override void WriteWarning(string message) + { + Console.WriteLine(string.Format("Warning: {0}", message)); + } + + public override void WriteReport(string name, string records) + { + File.WriteAllText(name, records); + } + } +} diff --git a/tools/StaticAnalysis/Decorator.cs b/tools/StaticAnalysis/Decorator.cs new file mode 100644 index 000000000000..b714c13def6e --- /dev/null +++ b/tools/StaticAnalysis/Decorator.cs @@ -0,0 +1,74 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; + +namespace StaticAnalysis +{ + public class Decorator + { + Action _action; + string _name; + + protected Decorator(Action action, string name) + { + _action = action; + _name = name; + Inner = null; + } + + public static Decorator Create() + { + return new Decorator(r => { }, "default"); + } + + public void Apply(T record) + { + _action(record); + if (Inner != null) + { + Inner.Apply(record); + } + } + + public void AddDecorator(Action action, string name) + { + if (Inner == null) + { + Inner = new Decorator(action, name); + } + else + { + Inner.AddDecorator(action, name); + } + } + + public void Remove(string name) + { + if (Inner != null) + { + if (string.Equals(Inner._name, name)) + { + Inner = Inner.Inner; + } + else + { + Inner.Remove(name); + } + } + } + + protected Decorator Inner { get; set; } + } +} diff --git a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyLoader.cs b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyLoader.cs new file mode 100644 index 000000000000..7fb782956eb8 --- /dev/null +++ b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyLoader.cs @@ -0,0 +1,86 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Reflection; + +namespace StaticAnalysis.DependencyAnalyzer +{ + /// + /// A class using .Net remoting to load assemblies and retrieve information in a separate app domain + /// + public class AssemblyLoader : MarshalByRefObject + { + /// + /// Load the assembly in the reflection context by name. Will succeed if the referenced assembly name can + /// be found using default assembly loading rules (i.e. it is in the current directory or the GAC) + /// + /// The fullname of the assembly + /// Information on the given assembly, if it was loaded successfully, or null if there is an assembly + /// loading issue. + public AssemblyMetadata GetReflectedAssemblyInfo(string assemblyName) + { + AssemblyMetadata result = null; + try + { + result = new AssemblyMetadata(Assembly.ReflectionOnlyLoad(assemblyName)); + } + catch + { + } + + return result; + } + + /// + /// Load the assembly found at the given path in the reflection context and return assembly metadata + /// + /// The full path to the assembly file. + /// Assembly metadata if the assembly is loaded successfully, or null if there are load errors. + public AssemblyMetadata GetReflectedAssemblyFromFile(string assemblyPath) + { + AssemblyMetadata result = null; + try + { + return new AssemblyMetadata(Assembly.ReflectionOnlyLoadFrom(assemblyPath)); + } + catch + { + } + + return result; + } + + /// + /// Create a new AppDomain and create a remote instance of AssemblyLoader we can use there + /// + /// directory containing assemblies + /// A new appdomain, where assemblies can be loaded + /// A proxy to the AssemblyLoader running in the newly created app domain + public static AssemblyLoader Create(string directoryPath, out AppDomain testDomain) + { + var setup = new AppDomainSetup(); + setup.ApplicationBase = directoryPath; + setup.ApplicationName = "TestDomain"; + setup.ApplicationTrust = AppDomain.CurrentDomain.ApplicationTrust; + setup.DisallowApplicationBaseProbing = false; + setup.DisallowCodeDownload = false; + setup.DisallowBindingRedirects = false; + setup.DisallowPublisherPolicy = false; + testDomain = AppDomain.CreateDomain("TestDomain", null, setup); + return testDomain.CreateInstanceFromAndUnwrap(typeof(AssemblyLoader).Assembly.Location, + typeof(AssemblyLoader).FullName) as AssemblyLoader; + } + } +} diff --git a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyMetadata.cs b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyMetadata.cs new file mode 100644 index 000000000000..f64c5d62195f --- /dev/null +++ b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyMetadata.cs @@ -0,0 +1,67 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace StaticAnalysis.DependencyAnalyzer +{ + /// + /// Serializable assembly metadata class, used to return assembly information from a remote AppDomain + /// + [Serializable] + public class AssemblyMetadata + { + AssemblyName _name; + string _location; + IList _references; + + public AssemblyMetadata(Assembly assembly) + { + _name = assembly.GetName(); + _location = assembly.Location; + _references = new List(); + foreach (var child in assembly.GetReferencedAssemblies()) + { + _references.Add(child); + } + } + + /// + /// Path to the assembly. + /// + public string Location { get { return _location; } } + + /// + /// The assembly name + /// + /// The assembly name for this assembly, including name and version + public AssemblyName GetName() + { + return _name; + } + + /// + /// The list of referenced assemblies + /// + /// A list of assembly name references for all assemblies referenced in the assembly manifest + public IEnumerable GetReferencedAssemblies() + { + return _references; + } + } +} diff --git a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyNameComparer.cs b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyNameComparer.cs new file mode 100644 index 000000000000..ad5d03c72081 --- /dev/null +++ b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyNameComparer.cs @@ -0,0 +1,38 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace StaticAnalysis.DependencyAnalyzer +{ + public class AssemblyNameComparer : IEqualityComparer + { + public static string GetComparisonName(AssemblyName name) + { + return string.Format("{0}.v{1}", name.Name, name.Version); + } + + public bool Equals(AssemblyName x, AssemblyName y) + { + return StringComparer.OrdinalIgnoreCase.Equals(GetComparisonName(x), GetComparisonName(y)); + } + + public int GetHashCode(AssemblyName obj) + { + return StringComparer.OrdinalIgnoreCase.GetHashCode(GetComparisonName(obj)); + } + } +} diff --git a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyRecord.cs b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyRecord.cs new file mode 100644 index 000000000000..4555741aa1ca --- /dev/null +++ b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyRecord.cs @@ -0,0 +1,143 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace StaticAnalysis.DependencyAnalyzer +{ + public class AssemblyEqualityComparer : IEqualityComparer + { + + public bool Equals(AssemblyRecord x, AssemblyRecord y) + { + return x.Equals(y) && x.Location.Equals(y.Location, StringComparison.OrdinalIgnoreCase); + } + + public int GetHashCode(AssemblyRecord obj) + { + var hashKey= string.Format("{0} {1} {2} {3} {4}", obj.Name, obj.Version, obj.AssemblyFileMajorVersion, + obj.AssemblyFileMinorVersion, obj.Location); + return hashKey.GetHashCode(); + } + } + /// + /// Information about references to an assembly + /// + public class AssemblyRecord : ICloneable + { + HashSet _parents = new HashSet(); + IList _children = new List(); + /// + /// The path to the assembly + /// + public string Location { get; set; } + + public string Name { get { return AssemblyName.Name; } } + public Version Version { get { return AssemblyName.Version; } } + public HashSet ReferencingAssembly { get {return _parents;} } + + public IList Children { get { return _children;} } + + public AssemblyName AssemblyName { get; set; } + + /// + /// The majorVersion portion of the file version for the assembly. This may or may not match the assembly version + /// + public int AssemblyFileMajorVersion { get; set; } + + /// + /// The minorVersion portion of the file version for the assembly file. This may or may not match the corresponding part of + /// the assembly version. + /// + public int AssemblyFileMinorVersion { get; set; } + + public override bool Equals(object obj) + { + var assembly = obj as AssemblyRecord; + if (assembly != null) + { + return string.Equals(assembly.Name, Name, StringComparison.OrdinalIgnoreCase) && + assembly.Version == Version; + } + + return false; + } + + public object Clone() + { + var copiedParents = new HashSet(); + foreach (var parent in _parents) + { + copiedParents.Add(parent.Clone() as AssemblyRecord); + } + + return new AssemblyRecord() + { + _parents = copiedParents, + AssemblyFileMajorVersion = AssemblyFileMajorVersion, + AssemblyFileMinorVersion = AssemblyFileMinorVersion, + AssemblyName = AssemblyName, + Location = Location + }; + + } + + public bool Equals(AssemblyName assembly) + { + return string.Equals(assembly.Name, Name, StringComparison.OrdinalIgnoreCase) && + assembly.Version == Version; + } + + public bool Equals(AssemblyRecord record) + { + return Equals(record.AssemblyName) + && AssemblyFileMajorVersion == record.AssemblyFileMajorVersion + && AssemblyFileMinorVersion == record.AssemblyFileMinorVersion; + } + + public override string ToString() + { + StringBuilder output = new StringBuilder(); + output.AppendLine(string.Format("AssemblyName: {0}, Version:{1}, FileVersion: {2}.{3}, Location:{4}", Name, Version, + AssemblyFileMajorVersion, AssemblyFileMinorVersion, Location)); + if (ReferencingAssembly.Any()) + { + output.AppendFormat("-> Parents: ({0})", string.Join(", ", ReferencingAssembly)); + } + + return output.ToString(); + + } + + public HashSet GetAncestors() + { + var result = new HashSet(); + foreach (var parent in ReferencingAssembly) + { + result.Add(parent); + foreach (var grandParent in parent.GetAncestors()) + { + result.Add(grandParent); + } + + } + + return result; + } + } +} diff --git a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyVersionConflict.cs b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyVersionConflict.cs new file mode 100644 index 000000000000..229f69fbd32c --- /dev/null +++ b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyVersionConflict.cs @@ -0,0 +1,80 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; + +namespace StaticAnalysis.DependencyAnalyzer +{ + /// + /// Indicates a conflict between assembly reference and actual assembly in a directory + /// + public class AssemblyVersionConflict : IReportRecord + { + /// + /// The directory containing the conflict + /// + public string Directory { get; set; } + + /// + /// The name of the assembly + /// + public string AssemblyName { get; set; } + + /// + /// The version referenced in the parent assembly manifest + /// + public Version ExpectedVersion { get; set; } + + /// + /// The version of the assembly on disk + /// + public Version ActualVersion { get; set; } + + /// + /// The identity of the parent assembly + /// + public string ParentAssembly { get; set; } + + /// + /// A textual description of the problem + /// + public string Description { get; set; } + + /// + /// A textual description of steps to remediate the issue + /// + public string Remediation { get; set; } + + public int Severity { get; set; } + + public string PrintHeaders() + { + return + "\"Directory\",\"AssemblyName\",\"Expected Version\",\"Actual Version\",\"Parent Assembly\",\"Severity\",\"Description\",\"Remediation\""; + } + + public string FormatRecord() + { + return + string.Format( + "\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\"", + Directory, AssemblyName, ExpectedVersion, ActualVersion, ParentAssembly, Severity, Description, Remediation); + } + + public override string ToString() + { + return FormatRecord(); + } + } +} diff --git a/tools/StaticAnalysis/DependencyAnalyzer/DependencyAnalyzer.cs b/tools/StaticAnalysis/DependencyAnalyzer/DependencyAnalyzer.cs new file mode 100644 index 000000000000..f05422fdc1e1 --- /dev/null +++ b/tools/StaticAnalysis/DependencyAnalyzer/DependencyAnalyzer.cs @@ -0,0 +1,310 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace StaticAnalysis.DependencyAnalyzer +{ + public class DependencyAnalyzer : IStaticAnalyzer + { + private Dictionary _assemblies = + new Dictionary(StringComparer.OrdinalIgnoreCase); + private Dictionary _sharedAssemblyReferences = + new Dictionary(new AssemblyNameComparer()); + private Dictionary _identicalSharedAssemblies = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + private AppDomain _testDomain; + private AssemblyLoader _loader; + private ReportLogger _versionConflictLogger; + private ReportLogger _sharedConflictLogger; + private ReportLogger _missingAssemblyLogger; + private ReportLogger _extraAssemblyLogger; + + public DependencyAnalyzer() + { + Name = "Dependency Analyzer"; + } + + public AnalysisLogger Logger { get; set; } + public string Name { get; private set; } + + public void Analyze(IEnumerable directories) + { + _versionConflictLogger = Logger.CreateLogger("AssemblyVersionConflict.csv"); + _sharedConflictLogger = Logger.CreateLogger("SharedAssemblyConflict.csv"); + _missingAssemblyLogger = Logger.CreateLogger("MissingAssemblies.csv"); + _extraAssemblyLogger = Logger.CreateLogger("ExtraAssemblies.csv"); + foreach (var baseDirectory in directories) + { + foreach (var directoryPath in Directory.EnumerateDirectories(baseDirectory)) + { + if (!Directory.Exists(directoryPath)) + { + throw new InvalidOperationException("Please pass a valid directory name as the first parameter"); + } + + Logger.WriteMessage("Processing Directory {0}", directoryPath); + _assemblies.Clear(); + _versionConflictLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; } , "Directory"); + _missingAssemblyLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; } , "Directory"); + _extraAssemblyLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; } , "Directory"); + ProcessDirectory(directoryPath); + _versionConflictLogger.Decorator.Remove("Directory"); + _missingAssemblyLogger.Decorator.Remove("Directory"); + _extraAssemblyLogger.Decorator.Remove("Directory"); + } + } + } + + private AssemblyRecord CreateAssemblyRecord(string path) + { + AssemblyRecord result = null; + var fullPath = Path.GetFullPath(path); + try + { + var assembly = LoadByReflectionFromFile(fullPath); + var versionInfo = FileVersionInfo.GetVersionInfo(fullPath); + result = new AssemblyRecord() + { + AssemblyName = assembly.GetName(), + AssemblyFileMajorVersion = versionInfo.FileMajorPart, + AssemblyFileMinorVersion = versionInfo.FileMinorPart, + Location = fullPath + }; + + foreach (var child in assembly.GetReferencedAssemblies()) + { + result.Children.Add(child); + } + } + catch + { + Logger.WriteError("Error loading assembly {0}", fullPath); + } + + return result; + } + + private bool AddSharedAssembly(AssemblyRecord assembly) + { + if (_sharedAssemblyReferences.ContainsKey(assembly.AssemblyName)) + { + var stored = _sharedAssemblyReferences[assembly.AssemblyName]; + if (!assembly.Equals(stored) && !(IsFrameworkAssembly(assembly.AssemblyName) && assembly.Version.Major <= 4)) + { + _sharedConflictLogger.LogRecord( new SharedAssemblyConflict + { + AssemblyName = assembly.Name, + AssemblyPathsAndFileVersions = new List>() + { + new Tuple(assembly.Location, new Version(assembly.AssemblyFileMajorVersion, + assembly.AssemblyFileMinorVersion)), + new Tuple(stored.Location, new Version(stored.AssemblyFileMajorVersion, + stored.AssemblyFileMinorVersion)) + + }, + AssemblyVersion = assembly.Version, + Severity = 0, + Description = "Shared assembly conflict, shared assemblies with the same assembly version have differing file versions", + Remediation=string.Format("Update the assembly reference for {0} in one of the referring assemblies", assembly.Name) + }); + + return false; + } + } + else + { + _sharedAssemblyReferences[assembly.AssemblyName] = assembly; + } + + return true; + } + + private AssemblyMetadata LoadByReflectionFromFile(string assemblyPath) + { + var info = _loader.GetReflectedAssemblyFromFile(assemblyPath); + if (info == null) + { + throw new InvalidOperationException(); + } + + return info; + } + + private bool AddSharedAssemblyExactVersion(AssemblyRecord record) + { + if (_identicalSharedAssemblies.ContainsKey(record.Name)) + { + var stored = _identicalSharedAssemblies[record.Name]; + if (!record.Equals(stored) && !(IsFrameworkAssembly(record.AssemblyName))) + { + _sharedConflictLogger.LogRecord( new SharedAssemblyConflict + { + AssemblyName = record.Name, + AssemblyVersion = record.Version, + Severity = 0, + AssemblyPathsAndFileVersions = new List>() + { + new Tuple(record.Location, new Version(record.AssemblyFileMajorVersion, + record.AssemblyFileMinorVersion)), + new Tuple(stored.Location, new Version(stored.AssemblyFileMajorVersion, + stored.AssemblyFileMinorVersion)), + }, + Description = string.Format("Assembly {0} has multiple versions as specified in 'Target'", record.Name), + Remediation = string.Format("Ensure that all packages reference exactly the same package version of {0}", record.Name) + + }); + + return false; + } + } + else + { + _identicalSharedAssemblies[record.Name] = record; + } + + return true; + } + + private static bool RequiresExactVersionMatch(AssemblyRecord name) + { + return name.Name.Contains("Microsoft.Azure.Common.Authentication"); + } + + private static bool IsFrameworkAssembly(AssemblyName name) + { + return name.Name.StartsWith("System") || name.Name.Equals("mscorlib"); + } + + private void ProcessDirectory(string directoryPath) + { + var savedDirectory = Directory.GetCurrentDirectory(); + Directory.SetCurrentDirectory(directoryPath); + _loader = AssemblyLoader.Create(directoryPath, out _testDomain); + foreach (var file in Directory.GetFiles(directoryPath).Where(file => file.EndsWith(".dll"))) + { + AssemblyRecord assembly = CreateAssemblyRecord(file); + _assemblies[assembly.Name] = assembly; + if (RequiresExactVersionMatch(assembly)) + { + AddSharedAssemblyExactVersion(assembly); + } + else + { + AddSharedAssembly(assembly); + } + } + + // Now check for assembly mismatches + foreach (var assembly in _assemblies.Values) + { + foreach (var reference in assembly.Children) + { + CheckAssemblyReference(reference, assembly); + } + } + + FindExtraAssemblies(); + + AppDomain.Unload(_testDomain); + Directory.SetCurrentDirectory(savedDirectory); + } + + private static bool IsCommandAssembly(AssemblyRecord assembly) + { + return assembly.Name.Contains("Commands") || assembly.Name.Contains("Cmdlets"); + } + private void FindExtraAssemblies() + { + if (_assemblies.Values.Any(a => !IsCommandAssembly(a) && ( a.ReferencingAssembly == null || a.ReferencingAssembly.Count == 0 || + !a.GetAncestors().Any(IsCommandAssembly)))) + { + foreach ( + var assembly in + _assemblies.Values.Where(a => !IsCommandAssembly(a) && (a.ReferencingAssembly == null || + a.ReferencingAssembly.Count == 0 || !a.GetAncestors().Any(IsCommandAssembly)))) + { + _extraAssemblyLogger.LogRecord(new ExtraAssembly + { + AssemblyName = assembly.Name, + Severity = 2, + Description = string.Format("Assembly {0} is not referenced from any cmdlets assembly", assembly.Name), + Remediation = string.Format("Remove assembly {0} from the project and regenerate the wix file", assembly.Name) + }); + } + } + } + + private void CheckAssemblyReference(AssemblyName reference, AssemblyRecord parent) + { + if (_assemblies.ContainsKey(reference.Name)) + { + var stored = _assemblies[reference.Name]; + if (stored.Equals(reference)) + { + stored.ReferencingAssembly.Add(parent); + } + else if (reference.Version.Major == 0 && reference.Version.Minor == 0) + { + Logger.WriteWarning("{0}.dll has reference to assembly {1} without any version specification.", parent.Name, reference.Name); + _versionConflictLogger.LogRecord(new AssemblyVersionConflict() + { + AssemblyName = reference.Name, + ActualVersion = stored.Version, + ExpectedVersion = reference.Version, + ParentAssembly = parent.Name, + Severity = 2, + Description = string.Format("Assembly {0} referenced from {1}.dll does not specify any assembly version evidence. " + + "The assembly will use version {2} from disk.", reference.Name, parent.Name, stored.Version), + Remediation = string.Format("Update the reference to assembly {0} from {1} so that assembly version evidence is " + + "supplied", reference.Name, parent.Name) + }); + } + else + { + var minVersion = (stored.Version < reference.Version) ? stored.Version : reference.Version; + _versionConflictLogger.LogRecord(new AssemblyVersionConflict() + { + AssemblyName = reference.Name, + ActualVersion = stored.Version, + ExpectedVersion = reference.Version, + ParentAssembly = parent.Name, + Severity = 1, + Description = string.Format("Assembly {0} version {1} referenced from {2}.dll does not match assembly version on " + + "disk: {3}", reference.Name, reference.Version, parent.Name, stored.Version), + Remediation = string.Format("Update any references to version {0} of assembly {1}", minVersion, reference.Name) + }); + } + } + else if (!IsFrameworkAssembly(reference)) + { + _missingAssemblyLogger.LogRecord(new MissingAssembly + { + AssemblyName = reference.Name, + AssemblyVersion = reference.Version.ToString(), + ReferencingAssembly = parent.Name, + Severity = 0, + Description = string.Format("Missing assembly {0} referenced from {1}", reference.Name, parent.Name), + Remediation = "Ensure that the assembly is included in the wix file or directory" + }); + } + } + } +} diff --git a/tools/StaticAnalysis/DependencyAnalyzer/ExtraAssembly.cs b/tools/StaticAnalysis/DependencyAnalyzer/ExtraAssembly.cs new file mode 100644 index 000000000000..18a14ed778ae --- /dev/null +++ b/tools/StaticAnalysis/DependencyAnalyzer/ExtraAssembly.cs @@ -0,0 +1,48 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; + +namespace StaticAnalysis.DependencyAnalyzer +{ + public class ExtraAssembly : IReportRecord + { + public string Directory { get; set; } + + public string AssemblyName { get; set; } + + public int Severity { get; set; } + + public string Description { get; set; } + + public string Remediation { get; set; } + + + public string PrintHeaders() + { + return "\"Directory\",\"AssemblyName\",\"Severity\",\"Description\",\"Remediation\""; + } + + public string FormatRecord() + { + return string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\"", + Directory, AssemblyName, Severity, Description, Remediation); + } + + public override string ToString() + { + return FormatRecord(); + } + } +} diff --git a/tools/StaticAnalysis/DependencyAnalyzer/MissingAssembly.cs b/tools/StaticAnalysis/DependencyAnalyzer/MissingAssembly.cs new file mode 100644 index 000000000000..91fbb25901b8 --- /dev/null +++ b/tools/StaticAnalysis/DependencyAnalyzer/MissingAssembly.cs @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +namespace StaticAnalysis.DependencyAnalyzer +{ + /// + /// Indicates an assembly that is not in the dependency tree for a cmdlet assembly + /// + public class MissingAssembly : IReportRecord + { + public string Directory { get; set; } + public string AssemblyName { get; set; } + public string AssemblyVersion { get; set; } + public string ReferencingAssembly { get; set; } + public string Description { get; set; } + public string Remediation { get; set; } + public int Severity { get; set; } + + public string PrintHeaders() + { + return "\"Directory\",\"Assembly Name\",\"Assembly Version\",\"Referencing Assembly\",\"Severity\",\"Description\",\"Remediation\""; + } + + public string FormatRecord() + { + return string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\"", + Directory, AssemblyName, AssemblyVersion, ReferencingAssembly, Severity, Description, Remediation); + } + + public override string ToString() + { + return FormatRecord(); + } + } +} diff --git a/tools/StaticAnalysis/DependencyAnalyzer/SharedAssemblyConflict.cs b/tools/StaticAnalysis/DependencyAnalyzer/SharedAssemblyConflict.cs new file mode 100644 index 000000000000..71ecbfc03667 --- /dev/null +++ b/tools/StaticAnalysis/DependencyAnalyzer/SharedAssemblyConflict.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StaticAnalysis.DependencyAnalyzer +{ + /// + /// Indicates a difference in assembly file versions for shared assemblies with the same assembly version. + /// This could result in unexpected behavior, depending on the assembly load order. + /// + public class SharedAssemblyConflict : IReportRecord + { + public string AssemblyName { get; set; } + public Version AssemblyVersion { get; set; } + public List> AssemblyPathsAndFileVersions { get; set; } + public string Description { get; set; } + public string Remediation { get; set; } + public int Severity { get; set; } + + public string PrintHeaders() + { + return "\"Target\",\"AssemblyName\",\"AssemblyVersion\",\"Severity\",\"Description\",\"Remediation\""; + } + + public string FormatRecord() + { + var targets = + AssemblyPathsAndFileVersions.Select(s => string.Format("File version {0} in {1}", s.Item2, s.Item1)); + var targetString = string.Join(", ", targets); + return string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\"", targetString, AssemblyName, + AssemblyVersion, Severity, Description, Remediation); + } + + public override string ToString() + { + return FormatRecord(); + } + } +} diff --git a/tools/StaticAnalysis/IReportRecord.cs b/tools/StaticAnalysis/IReportRecord.cs new file mode 100644 index 000000000000..aa362689b4dd --- /dev/null +++ b/tools/StaticAnalysis/IReportRecord.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; + +namespace StaticAnalysis +{ + public interface IReportRecord + { + string Description { get; set; } + string Remediation { get; set; } + int Severity { get; set; } + string PrintHeaders(); + string FormatRecord(); + } +} diff --git a/tools/StaticAnalysis/IStaticAnalyzer.cs b/tools/StaticAnalysis/IStaticAnalyzer.cs new file mode 100644 index 000000000000..e6c4806f4766 --- /dev/null +++ b/tools/StaticAnalysis/IStaticAnalyzer.cs @@ -0,0 +1,41 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; + +namespace StaticAnalysis +{ + public interface IStaticAnalyzer + { + /// + /// The logger where validation records should be written + /// + AnalysisLogger Logger { get; set; } + + /// + /// The display name of the Analyzer + /// + string Name { get; } + + /// + /// Validate the given assembly in the given directory + /// + /// The analysis targets + void Analyze(IEnumerable scopes); + } +} diff --git a/tools/StaticAnalysis/Program.cs b/tools/StaticAnalysis/Program.cs new file mode 100644 index 000000000000..2861069fe443 --- /dev/null +++ b/tools/StaticAnalysis/Program.cs @@ -0,0 +1,57 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.IO; + +namespace StaticAnalysis.DependencyAnalyzer +{ + public class Program + { + public static void Main(string[] args) + { + if (args == null || args.Length < 1) + { + throw new InvalidOperationException("Please pass a valid directory name as the first parameter"); + } + + var installDir = args[0]; + if (!Directory.Exists(installDir)) + { + throw new InvalidOperationException("You must pass a valid directory as the first parameter"); + } + + var directories = new List + { + Path.Combine(installDir, @"ResourceManager\AzureResourceManager\"), + Path.Combine(installDir, @"ServiceManagement\Azure\") + }; + + var reportsDirectory = Directory.GetCurrentDirectory(); + if (args.Length > 1 && Directory.Exists(args[1])) + { + reportsDirectory = args[1]; + } + + + var logger = new ConsoleLogger(reportsDirectory); + var analyzer = new DependencyAnalyzer {Logger = logger}; + logger.WriteMessage("Executing analyzer: {0}", analyzer.Name); + analyzer.Analyze(directories); + logger.WriteReports(); + logger.WriteMessage("Processing complete for analyzer: {0}", analyzer.Name); + } + } +} diff --git a/tools/StaticAnalysis/Properties/AssemblyInfo.cs b/tools/StaticAnalysis/Properties/AssemblyInfo.cs new file mode 100644 index 000000000000..2dffc52f6682 --- /dev/null +++ b/tools/StaticAnalysis/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DependencyChecker")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DependencyChecker")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4c8c8997-993d-4351-b7bc-ab423356fd7f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tools/StaticAnalysis/ReportLogger.cs b/tools/StaticAnalysis/ReportLogger.cs new file mode 100644 index 000000000000..5106866e4e8b --- /dev/null +++ b/tools/StaticAnalysis/ReportLogger.cs @@ -0,0 +1,83 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace StaticAnalysis +{ + /// + /// Abstract report logger - used an an abstract handle to write reports from typed loggers + /// + public abstract class ReportLogger + { + private AnalysisLogger _parent; + private string _outputFile; + public ReportLogger(string fileName, AnalysisLogger parent) + { + _parent = parent; + _outputFile = fileName; + } + + protected AnalysisLogger ParentLogger { get { return _parent; } } + public string FileName { get { return _outputFile; } } + public abstract IList Records { get; } + + public virtual void WriteError(string error) + { + ParentLogger.WriteError(error); + } + + public virtual void WriteMessage(string message) + { + ParentLogger.WriteMessage(message); + } + + public virtual void WriteWarning(string message) + { + ParentLogger.WriteWarning(message); + } + } + + /// + /// A typed report logger + /// + /// + public class ReportLogger : ReportLogger where T:IReportRecord, new() + { + public ReportLogger(string fileName, AnalysisLogger logger) : base(fileName, logger) + { + Decorator = Decorator.Create(); + } + + private IList _records = new List(); + public Decorator Decorator { get; protected set; } + + /// + /// Log a record to the report + /// + /// + public void LogRecord(T record) + { + Decorator.Apply(record); + _records.Add(record); + } + + public override IList Records + { + get { return _records.Select(r => r as IReportRecord).ToList(); } + } + } +} diff --git a/tools/StaticAnalysis/Static Analysis.csproj b/tools/StaticAnalysis/Static Analysis.csproj new file mode 100644 index 000000000000..63c60ce4ec69 --- /dev/null +++ b/tools/StaticAnalysis/Static Analysis.csproj @@ -0,0 +1,70 @@ + + + + + Debug + AnyCPU + {68384B59-BA0C-4B7B-B3F6-9C7988296C16} + Exe + Properties + StaticAnalysis + StaticAnalysis + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/StaticAnalysis/Static Analysis.sln b/tools/StaticAnalysis/Static Analysis.sln new file mode 100644 index 000000000000..8b59a0384e02 --- /dev/null +++ b/tools/StaticAnalysis/Static Analysis.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Static Analysis", "Static Analysis.csproj", "{68384B59-BA0C-4B7B-B3F6-9C7988296C16}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {68384B59-BA0C-4B7B-B3F6-9C7988296C16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68384B59-BA0C-4B7B-B3F6-9C7988296C16}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68384B59-BA0C-4B7B-B3F6-9C7988296C16}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68384B59-BA0C-4B7B-B3F6-9C7988296C16}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal From c08cd34d5e05a5c397e39b8431cdeed2fe23ef1f Mon Sep 17 00:00:00 2001 From: markcowl Date: Mon, 1 Feb 2016 12:42:27 -0800 Subject: [PATCH 2/6] responding to review feedback --- tools/StaticAnalysis/AnalysisLogger.cs | 26 +++++- tools/StaticAnalysis/ConsoleLogger.cs | 12 +-- tools/StaticAnalysis/Decorator.cs | 6 +- .../DependencyAnalyzer/AssemblyLoader.cs | 29 +++++-- .../DependencyAnalyzer/AssemblyMetadata.cs | 6 +- .../AssemblyNameComparer.cs | 24 +++++- .../DependencyAnalyzer/AssemblyRecord.cs | 36 ++++---- .../AssemblyVersionConflict.cs | 8 +- .../DependencyAnalyzer/DependencyAnalyzer.cs | 83 +++++++++++-------- .../DependencyAnalyzer/ExtraAssembly.cs | 7 +- .../DependencyAnalyzer/MissingAssembly.cs | 8 +- .../SharedAssemblyConflict.cs | 24 ++++-- tools/StaticAnalysis/IReportRecord.cs | 23 +++-- tools/StaticAnalysis/IStaticAnalyzer.cs | 7 +- tools/StaticAnalysis/Program.cs | 15 +++- tools/StaticAnalysis/ReportLogger.cs | 12 +-- 16 files changed, 211 insertions(+), 115 deletions(-) diff --git a/tools/StaticAnalysis/AnalysisLogger.cs b/tools/StaticAnalysis/AnalysisLogger.cs index abff4f02131a..9c7091565e9e 100644 --- a/tools/StaticAnalysis/AnalysisLogger.cs +++ b/tools/StaticAnalysis/AnalysisLogger.cs @@ -1,14 +1,27 @@ -using System; +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using System.Threading.Tasks; namespace StaticAnalysis { /// - /// Abstract class to implement the logging structure + /// Abstract class to implement the report logging structure /// public abstract class AnalysisLogger { @@ -72,8 +85,13 @@ public virtual void WriteWarning(string format, params object[] args) /// The type of records written to the log /// The filename (without file path) where the report will be written /// The given logger. Analyzer may write records to this logger and they will be written to the report file. - public virtual ReportLogger CreateLogger(string fileName) where T: IReportRecord, new() + public virtual ReportLogger CreateLogger(string fileName) where T : IReportRecord, new() { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException("fileName"); + } + var filePath = Path.Combine(_baseDirectory, fileName); var logger = new ReportLogger(filePath, this); Loggers.Add(logger); diff --git a/tools/StaticAnalysis/ConsoleLogger.cs b/tools/StaticAnalysis/ConsoleLogger.cs index 4fa3180f11a7..1cb18d070d64 100644 --- a/tools/StaticAnalysis/ConsoleLogger.cs +++ b/tools/StaticAnalysis/ConsoleLogger.cs @@ -13,24 +13,24 @@ // ---------------------------------------------------------------------------------- using System; -using System.Collections.Generic; using System.IO; namespace StaticAnalysis { /// - /// Simple class for logging information + /// Simple class for logging errors and warnings to the console and writing reports to the file system. /// public class ConsoleLogger : AnalysisLogger { - public ConsoleLogger(string baseDirectory) : base(baseDirectory) + public ConsoleLogger(string baseDirectory) + : base(baseDirectory) { } - public override void WriteError(string error) + public override void WriteError(string error) { - Console.WriteLine(string.Format("### ERROR {0}", error)); + Console.WriteLine("### ERROR {0}", error); } public override void WriteMessage(string message) @@ -40,7 +40,7 @@ public override void WriteMessage(string message) public override void WriteWarning(string message) { - Console.WriteLine(string.Format("Warning: {0}", message)); + Console.WriteLine("Warning: {0}", message); } public override void WriteReport(string name, string records) diff --git a/tools/StaticAnalysis/Decorator.cs b/tools/StaticAnalysis/Decorator.cs index b714c13def6e..64dc281e26fe 100644 --- a/tools/StaticAnalysis/Decorator.cs +++ b/tools/StaticAnalysis/Decorator.cs @@ -16,6 +16,10 @@ namespace StaticAnalysis { + /// + /// Abstract class to implement the Decorator pattern + /// + /// public class Decorator { Action _action; @@ -31,7 +35,7 @@ protected Decorator(Action action, string name) public static Decorator Create() { return new Decorator(r => { }, "default"); - } + } public void Apply(T record) { diff --git a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyLoader.cs b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyLoader.cs index 7fb782956eb8..15cd6731a4c8 100644 --- a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyLoader.cs +++ b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyLoader.cs @@ -18,7 +18,7 @@ namespace StaticAnalysis.DependencyAnalyzer { /// - /// A class using .Net remoting to load assemblies and retrieve information in a separate app domain + /// A class using .Net Remoting to load assemblies and retrieve information in a separate app domain /// public class AssemblyLoader : MarshalByRefObject { @@ -26,11 +26,16 @@ public class AssemblyLoader : MarshalByRefObject /// Load the assembly in the reflection context by name. Will succeed if the referenced assembly name can /// be found using default assembly loading rules (i.e. it is in the current directory or the GAC) /// - /// The fullname of the assembly - /// Information on the given assembly, if it was loaded successfully, or null if there is an assembly - /// loading issue. - public AssemblyMetadata GetReflectedAssemblyInfo(string assemblyName) + /// The full name of the assembly + /// Information on the given assembly, if it was loaded successfully, or null if there is an + /// assembly loading issue. + public AssemblyMetadata GetReflectedAssemblyInfo(string assemblyName) { + if (string.IsNullOrWhiteSpace(assemblyName)) + { + throw new ArgumentException("assemblyName"); + } + AssemblyMetadata result = null; try { @@ -50,6 +55,11 @@ public AssemblyMetadata GetReflectedAssemblyInfo(string assemblyName) /// Assembly metadata if the assembly is loaded successfully, or null if there are load errors. public AssemblyMetadata GetReflectedAssemblyFromFile(string assemblyPath) { + if (string.IsNullOrWhiteSpace(assemblyPath)) + { + throw new ArgumentException("assemblyPath"); + } + AssemblyMetadata result = null; try { @@ -66,10 +76,15 @@ public AssemblyMetadata GetReflectedAssemblyFromFile(string assemblyPath) /// Create a new AppDomain and create a remote instance of AssemblyLoader we can use there /// /// directory containing assemblies - /// A new appdomain, where assemblies can be loaded + /// A new AppDomain, where assemblies can be loaded /// A proxy to the AssemblyLoader running in the newly created app domain public static AssemblyLoader Create(string directoryPath, out AppDomain testDomain) { + if (string.IsNullOrWhiteSpace(directoryPath)) + { + throw new ArgumentException("directoryPath"); + } + var setup = new AppDomainSetup(); setup.ApplicationBase = directoryPath; setup.ApplicationName = "TestDomain"; @@ -79,7 +94,7 @@ public static AssemblyLoader Create(string directoryPath, out AppDomain testDoma setup.DisallowBindingRedirects = false; setup.DisallowPublisherPolicy = false; testDomain = AppDomain.CreateDomain("TestDomain", null, setup); - return testDomain.CreateInstanceFromAndUnwrap(typeof(AssemblyLoader).Assembly.Location, + return testDomain.CreateInstanceFromAndUnwrap(typeof(AssemblyLoader).Assembly.Location, typeof(AssemblyLoader).FullName) as AssemblyLoader; } } diff --git a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyMetadata.cs b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyMetadata.cs index f64c5d62195f..6e3abe15e585 100644 --- a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyMetadata.cs +++ b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyMetadata.cs @@ -14,9 +14,7 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Threading.Tasks; namespace StaticAnalysis.DependencyAnalyzer { @@ -40,7 +38,7 @@ public AssemblyMetadata(Assembly assembly) _references.Add(child); } } - + /// /// Path to the assembly. /// @@ -49,7 +47,7 @@ public AssemblyMetadata(Assembly assembly) /// /// The assembly name /// - /// The assembly name for this assembly, including name and version + /// The assembly name for this assembly, including name and the version public AssemblyName GetName() { return _name; diff --git a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyNameComparer.cs b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyNameComparer.cs index ad5d03c72081..1f0d1fa87414 100644 --- a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyNameComparer.cs +++ b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyNameComparer.cs @@ -18,16 +18,34 @@ namespace StaticAnalysis.DependencyAnalyzer { + /// + /// Equality comparer, used to uniquely store assembly records by assembly name + /// public class AssemblyNameComparer : IEqualityComparer { - public static string GetComparisonName(AssemblyName name) + private static string GetComparisonName(AssemblyName name) { + if (name == null) + { + throw new ArgumentNullException("name"); + } + return string.Format("{0}.v{1}", name.Name, name.Version); } - public bool Equals(AssemblyName x, AssemblyName y) + public bool Equals(AssemblyName assembly1, AssemblyName assembly2) { - return StringComparer.OrdinalIgnoreCase.Equals(GetComparisonName(x), GetComparisonName(y)); + if (assembly1 == null) + { + throw new ArgumentNullException("assembly1"); + } + + if (assembly2 == null) + { + throw new ArgumentNullException("assembly2"); + } + + return StringComparer.OrdinalIgnoreCase.Equals(GetComparisonName(assembly1), GetComparisonName(assembly2)); } public int GetHashCode(AssemblyName obj) diff --git a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyRecord.cs b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyRecord.cs index 4555741aa1ca..cc02a9d08de5 100644 --- a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyRecord.cs +++ b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyRecord.cs @@ -20,23 +20,8 @@ namespace StaticAnalysis.DependencyAnalyzer { - public class AssemblyEqualityComparer : IEqualityComparer - { - - public bool Equals(AssemblyRecord x, AssemblyRecord y) - { - return x.Equals(y) && x.Location.Equals(y.Location, StringComparison.OrdinalIgnoreCase); - } - - public int GetHashCode(AssemblyRecord obj) - { - var hashKey= string.Format("{0} {1} {2} {3} {4}", obj.Name, obj.Version, obj.AssemblyFileMajorVersion, - obj.AssemblyFileMinorVersion, obj.Location); - return hashKey.GetHashCode(); - } - } /// - /// Information about references to an assembly + /// Information about assemblies /// public class AssemblyRecord : ICloneable { @@ -49,9 +34,9 @@ public class AssemblyRecord : ICloneable public string Name { get { return AssemblyName.Name; } } public Version Version { get { return AssemblyName.Version; } } - public HashSet ReferencingAssembly { get {return _parents;} } + public HashSet ReferencingAssembly { get { return _parents; } } - public IList Children { get { return _children;} } + public IList Children { get { return _children; } } public AssemblyName AssemblyName { get; set; } @@ -97,6 +82,11 @@ public object Clone() } + /// + /// Compare the assembly record to an assembly name + /// + /// The assembly name to compare with + /// True if the assembly name is a reference to this assembly, otherwise false public bool Equals(AssemblyName assembly) { return string.Equals(assembly.Name, Name, StringComparison.OrdinalIgnoreCase) && @@ -105,15 +95,15 @@ public bool Equals(AssemblyName assembly) public bool Equals(AssemblyRecord record) { - return Equals(record.AssemblyName) - && AssemblyFileMajorVersion == record.AssemblyFileMajorVersion + return Equals(record.AssemblyName) + && AssemblyFileMajorVersion == record.AssemblyFileMajorVersion && AssemblyFileMinorVersion == record.AssemblyFileMinorVersion; } public override string ToString() { StringBuilder output = new StringBuilder(); - output.AppendLine(string.Format("AssemblyName: {0}, Version:{1}, FileVersion: {2}.{3}, Location:{4}", Name, Version, + output.AppendLine(string.Format("AssemblyName: {0}, Version:{1}, FileVersion: {2}.{3}, Location:{4}", Name, Version, AssemblyFileMajorVersion, AssemblyFileMinorVersion, Location)); if (ReferencingAssembly.Any()) { @@ -124,6 +114,10 @@ public override string ToString() } + /// + /// Get all the ancestors in the ancestor tree + /// + /// The full set of ancestors in the ancestor tree. public HashSet GetAncestors() { var result = new HashSet(); diff --git a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyVersionConflict.cs b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyVersionConflict.cs index 229f69fbd32c..7c54d20f5571 100644 --- a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyVersionConflict.cs +++ b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyVersionConflict.cs @@ -22,7 +22,7 @@ namespace StaticAnalysis.DependencyAnalyzer public class AssemblyVersionConflict : IReportRecord { /// - /// The directory containing the conflict + /// The directory containing a conflict /// public string Directory { get; set; } @@ -61,7 +61,8 @@ public class AssemblyVersionConflict : IReportRecord public string PrintHeaders() { return - "\"Directory\",\"AssemblyName\",\"Expected Version\",\"Actual Version\",\"Parent Assembly\",\"Severity\",\"Description\",\"Remediation\""; + "\"Directory\",\"AssemblyName\",\"Expected Version\",\"Actual Version\",\"Parent Assembly\",\"Severity\"," + + "\"Description\",\"Remediation\""; } public string FormatRecord() @@ -69,7 +70,8 @@ public string FormatRecord() return string.Format( "\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\"", - Directory, AssemblyName, ExpectedVersion, ActualVersion, ParentAssembly, Severity, Description, Remediation); + Directory, AssemblyName, ExpectedVersion, ActualVersion, ParentAssembly, Severity, + Description, Remediation); } public override string ToString() diff --git a/tools/StaticAnalysis/DependencyAnalyzer/DependencyAnalyzer.cs b/tools/StaticAnalysis/DependencyAnalyzer/DependencyAnalyzer.cs index f05422fdc1e1..aac335ee1f7f 100644 --- a/tools/StaticAnalysis/DependencyAnalyzer/DependencyAnalyzer.cs +++ b/tools/StaticAnalysis/DependencyAnalyzer/DependencyAnalyzer.cs @@ -21,13 +21,16 @@ namespace StaticAnalysis.DependencyAnalyzer { + /// + /// The static analysis tool for ensuring no runtime conflicts in assembly dependencies + /// public class DependencyAnalyzer : IStaticAnalyzer { - private Dictionary _assemblies = + private Dictionary _assemblies = new Dictionary(StringComparer.OrdinalIgnoreCase); - private Dictionary _sharedAssemblyReferences = + private Dictionary _sharedAssemblyReferences = new Dictionary(new AssemblyNameComparer()); - private Dictionary _identicalSharedAssemblies = + private Dictionary _identicalSharedAssemblies = new Dictionary(StringComparer.OrdinalIgnoreCase); private AppDomain _testDomain; @@ -37,7 +40,7 @@ public class DependencyAnalyzer : IStaticAnalyzer private ReportLogger _missingAssemblyLogger; private ReportLogger _extraAssemblyLogger; - public DependencyAnalyzer() + public DependencyAnalyzer() { Name = "Dependency Analyzer"; } @@ -47,6 +50,11 @@ public DependencyAnalyzer() public void Analyze(IEnumerable directories) { + if (directories == null) + { + throw new ArgumentNullException("directories"); + } + _versionConflictLogger = Logger.CreateLogger("AssemblyVersionConflict.csv"); _sharedConflictLogger = Logger.CreateLogger("SharedAssemblyConflict.csv"); _missingAssemblyLogger = Logger.CreateLogger("MissingAssemblies.csv"); @@ -62,16 +70,16 @@ public void Analyze(IEnumerable directories) Logger.WriteMessage("Processing Directory {0}", directoryPath); _assemblies.Clear(); - _versionConflictLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; } , "Directory"); - _missingAssemblyLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; } , "Directory"); - _extraAssemblyLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; } , "Directory"); - ProcessDirectory(directoryPath); + _versionConflictLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; }, "Directory"); + _missingAssemblyLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; }, "Directory"); + _extraAssemblyLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; }, "Directory"); + ProcessDirectory(directoryPath); _versionConflictLogger.Decorator.Remove("Directory"); - _missingAssemblyLogger.Decorator.Remove("Directory"); + _missingAssemblyLogger.Decorator.Remove("Directory"); _extraAssemblyLogger.Decorator.Remove("Directory"); - } + } } - } + } private AssemblyRecord CreateAssemblyRecord(string path) { @@ -109,7 +117,7 @@ private bool AddSharedAssembly(AssemblyRecord assembly) var stored = _sharedAssemblyReferences[assembly.AssemblyName]; if (!assembly.Equals(stored) && !(IsFrameworkAssembly(assembly.AssemblyName) && assembly.Version.Major <= 4)) { - _sharedConflictLogger.LogRecord( new SharedAssemblyConflict + _sharedConflictLogger.LogRecord(new SharedAssemblyConflict { AssemblyName = assembly.Name, AssemblyPathsAndFileVersions = new List>() @@ -122,8 +130,10 @@ private bool AddSharedAssembly(AssemblyRecord assembly) }, AssemblyVersion = assembly.Version, Severity = 0, - Description = "Shared assembly conflict, shared assemblies with the same assembly version have differing file versions", - Remediation=string.Format("Update the assembly reference for {0} in one of the referring assemblies", assembly.Name) + Description = "Shared assembly conflict, shared assemblies with the same assembly " + + "version have differing file versions", + Remediation = string.Format("Update the assembly reference for {0} in one of the " + + "referring assemblies", assembly.Name) }); return false; @@ -155,7 +165,7 @@ private bool AddSharedAssemblyExactVersion(AssemblyRecord record) var stored = _identicalSharedAssemblies[record.Name]; if (!record.Equals(stored) && !(IsFrameworkAssembly(record.AssemblyName))) { - _sharedConflictLogger.LogRecord( new SharedAssemblyConflict + _sharedConflictLogger.LogRecord(new SharedAssemblyConflict { AssemblyName = record.Name, AssemblyVersion = record.Version, @@ -167,8 +177,10 @@ private bool AddSharedAssemblyExactVersion(AssemblyRecord record) new Tuple(stored.Location, new Version(stored.AssemblyFileMajorVersion, stored.AssemblyFileMinorVersion)), }, - Description = string.Format("Assembly {0} has multiple versions as specified in 'Target'", record.Name), - Remediation = string.Format("Ensure that all packages reference exactly the same package version of {0}", record.Name) + Description = string.Format("Assembly {0} has multiple versions as specified in 'Target'", + record.Name), + Remediation = string.Format("Ensure that all packages reference exactly the same package " + + "version of {0}", record.Name) }); @@ -231,14 +243,15 @@ private static bool IsCommandAssembly(AssemblyRecord assembly) { return assembly.Name.Contains("Commands") || assembly.Name.Contains("Cmdlets"); } + private void FindExtraAssemblies() { - if (_assemblies.Values.Any(a => !IsCommandAssembly(a) && ( a.ReferencingAssembly == null || a.ReferencingAssembly.Count == 0 || + if (_assemblies.Values.Any(a => !IsCommandAssembly(a) && (a.ReferencingAssembly == null || a.ReferencingAssembly.Count == 0 || !a.GetAncestors().Any(IsCommandAssembly)))) { foreach ( var assembly in - _assemblies.Values.Where(a => !IsCommandAssembly(a) && (a.ReferencingAssembly == null || + _assemblies.Values.Where(a => !IsCommandAssembly(a) && (a.ReferencingAssembly == null || a.ReferencingAssembly.Count == 0 || !a.GetAncestors().Any(IsCommandAssembly)))) { _extraAssemblyLogger.LogRecord(new ExtraAssembly @@ -246,7 +259,7 @@ var assembly in AssemblyName = assembly.Name, Severity = 2, Description = string.Format("Assembly {0} is not referenced from any cmdlets assembly", assembly.Name), - Remediation = string.Format("Remove assembly {0} from the project and regenerate the wix file", assembly.Name) + Remediation = string.Format("Remove assembly {0} from the project and regenerate the Wix file", assembly.Name) }); } } @@ -264,20 +277,20 @@ private void CheckAssemblyReference(AssemblyName reference, AssemblyRecord paren else if (reference.Version.Major == 0 && reference.Version.Minor == 0) { Logger.WriteWarning("{0}.dll has reference to assembly {1} without any version specification.", parent.Name, reference.Name); - _versionConflictLogger.LogRecord(new AssemblyVersionConflict() - { - AssemblyName = reference.Name, - ActualVersion = stored.Version, - ExpectedVersion = reference.Version, - ParentAssembly = parent.Name, - Severity = 2, - Description = string.Format("Assembly {0} referenced from {1}.dll does not specify any assembly version evidence. " + - "The assembly will use version {2} from disk.", reference.Name, parent.Name, stored.Version), - Remediation = string.Format("Update the reference to assembly {0} from {1} so that assembly version evidence is " + - "supplied", reference.Name, parent.Name) - }); - } - else + _versionConflictLogger.LogRecord(new AssemblyVersionConflict() + { + AssemblyName = reference.Name, + ActualVersion = stored.Version, + ExpectedVersion = reference.Version, + ParentAssembly = parent.Name, + Severity = 2, + Description = string.Format("Assembly {0} referenced from {1}.dll does not specify any assembly version evidence. " + + "The assembly will use version {2} from disk.", reference.Name, parent.Name, stored.Version), + Remediation = string.Format("Update the reference to assembly {0} from {1} so that assembly version evidence is " + + "supplied", reference.Name, parent.Name) + }); + } + else { var minVersion = (stored.Version < reference.Version) ? stored.Version : reference.Version; _versionConflictLogger.LogRecord(new AssemblyVersionConflict() @@ -302,7 +315,7 @@ private void CheckAssemblyReference(AssemblyName reference, AssemblyRecord paren ReferencingAssembly = parent.Name, Severity = 0, Description = string.Format("Missing assembly {0} referenced from {1}", reference.Name, parent.Name), - Remediation = "Ensure that the assembly is included in the wix file or directory" + Remediation = "Ensure that the assembly is included in the Wix file or directory" }); } } diff --git a/tools/StaticAnalysis/DependencyAnalyzer/ExtraAssembly.cs b/tools/StaticAnalysis/DependencyAnalyzer/ExtraAssembly.cs index 18a14ed778ae..677bb9271054 100644 --- a/tools/StaticAnalysis/DependencyAnalyzer/ExtraAssembly.cs +++ b/tools/StaticAnalysis/DependencyAnalyzer/ExtraAssembly.cs @@ -12,10 +12,11 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using System; - namespace StaticAnalysis.DependencyAnalyzer { + /// + /// Record for reporting assemblies that are not used by a Cmdlet assembly or its dependencies + /// public class ExtraAssembly : IReportRecord { public string Directory { get; set; } @@ -36,7 +37,7 @@ public string PrintHeaders() public string FormatRecord() { - return string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\"", + return string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\"", Directory, AssemblyName, Severity, Description, Remediation); } diff --git a/tools/StaticAnalysis/DependencyAnalyzer/MissingAssembly.cs b/tools/StaticAnalysis/DependencyAnalyzer/MissingAssembly.cs index 91fbb25901b8..ea73fd482404 100644 --- a/tools/StaticAnalysis/DependencyAnalyzer/MissingAssembly.cs +++ b/tools/StaticAnalysis/DependencyAnalyzer/MissingAssembly.cs @@ -15,7 +15,7 @@ namespace StaticAnalysis.DependencyAnalyzer { /// - /// Indicates an assembly that is not in the dependency tree for a cmdlet assembly + /// Record to indicate an assembly that is not in the dependency tree for a cmdlet assembly /// public class MissingAssembly : IReportRecord { @@ -29,13 +29,15 @@ public class MissingAssembly : IReportRecord public string PrintHeaders() { - return "\"Directory\",\"Assembly Name\",\"Assembly Version\",\"Referencing Assembly\",\"Severity\",\"Description\",\"Remediation\""; + return "\"Directory\",\"Assembly Name\",\"Assembly Version\",\"Referencing Assembly\"," + + "\"Severity\",\"Description\",\"Remediation\""; } public string FormatRecord() { return string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\"", - Directory, AssemblyName, AssemblyVersion, ReferencingAssembly, Severity, Description, Remediation); + Directory, AssemblyName, AssemblyVersion, ReferencingAssembly, + Severity, Description, Remediation); } public override string ToString() diff --git a/tools/StaticAnalysis/DependencyAnalyzer/SharedAssemblyConflict.cs b/tools/StaticAnalysis/DependencyAnalyzer/SharedAssemblyConflict.cs index 71ecbfc03667..f2b8b0b9b4e6 100644 --- a/tools/StaticAnalysis/DependencyAnalyzer/SharedAssemblyConflict.cs +++ b/tools/StaticAnalysis/DependencyAnalyzer/SharedAssemblyConflict.cs @@ -1,14 +1,26 @@ -using System; +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace StaticAnalysis.DependencyAnalyzer { /// - /// Indicates a difference in assembly file versions for shared assemblies with the same assembly version. - /// This could result in unexpected behavior, depending on the assembly load order. + /// Record to indicate a difference in assembly file versions for shared assemblies with the same + /// assembly version. This could result in unexpected behavior, depending on the assembly load order. /// public class SharedAssemblyConflict : IReportRecord { @@ -21,7 +33,7 @@ public class SharedAssemblyConflict : IReportRecord public string PrintHeaders() { - return "\"Target\",\"AssemblyName\",\"AssemblyVersion\",\"Severity\",\"Description\",\"Remediation\""; + return "\"Target\",\"AssemblyName\",\"AssemblyVersion\",\"Severity\",\"Description\",\"Remediation\""; } public string FormatRecord() diff --git a/tools/StaticAnalysis/IReportRecord.cs b/tools/StaticAnalysis/IReportRecord.cs index aa362689b4dd..ab0c97f50171 100644 --- a/tools/StaticAnalysis/IReportRecord.cs +++ b/tools/StaticAnalysis/IReportRecord.cs @@ -1,13 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- namespace StaticAnalysis { + /// + /// Abstract interface for static analysis reports. + /// public interface IReportRecord { string Description { get; set; } diff --git a/tools/StaticAnalysis/IStaticAnalyzer.cs b/tools/StaticAnalysis/IStaticAnalyzer.cs index e6c4806f4766..8eb55a0ec43b 100644 --- a/tools/StaticAnalysis/IStaticAnalyzer.cs +++ b/tools/StaticAnalysis/IStaticAnalyzer.cs @@ -12,14 +12,13 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using System; using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; namespace StaticAnalysis { + /// + /// Defines the public object model for a static analysis tool + /// public interface IStaticAnalyzer { /// diff --git a/tools/StaticAnalysis/Program.cs b/tools/StaticAnalysis/Program.cs index 2861069fe443..e4c94f164179 100644 --- a/tools/StaticAnalysis/Program.cs +++ b/tools/StaticAnalysis/Program.cs @@ -18,6 +18,9 @@ namespace StaticAnalysis.DependencyAnalyzer { + /// + /// Runner for all static analysis tools. + /// public class Program { public static void Main(string[] args) @@ -40,14 +43,22 @@ public static void Main(string[] args) }; var reportsDirectory = Directory.GetCurrentDirectory(); + bool logReportsDirectoryWarning = true; if (args.Length > 1 && Directory.Exists(args[1])) { reportsDirectory = args[1]; + logReportsDirectoryWarning = false; } - var logger = new ConsoleLogger(reportsDirectory); - var analyzer = new DependencyAnalyzer {Logger = logger}; + + if (logReportsDirectoryWarning) + { + logger.WriteWarning("No logger specified in the second parameter, writing reports to {0}", + reportsDirectory); + } + + var analyzer = new DependencyAnalyzer { Logger = logger }; logger.WriteMessage("Executing analyzer: {0}", analyzer.Name); analyzer.Analyze(directories); logger.WriteReports(); diff --git a/tools/StaticAnalysis/ReportLogger.cs b/tools/StaticAnalysis/ReportLogger.cs index 5106866e4e8b..c836be697c1c 100644 --- a/tools/StaticAnalysis/ReportLogger.cs +++ b/tools/StaticAnalysis/ReportLogger.cs @@ -12,14 +12,13 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using System; using System.Collections.Generic; using System.Linq; namespace StaticAnalysis { /// - /// Abstract report logger - used an an abstract handle to write reports from typed loggers + /// Abstract report logger - used as an abstraction over typed loggers. /// public abstract class ReportLogger { @@ -54,10 +53,11 @@ public virtual void WriteWarning(string message) /// /// A typed report logger /// - /// - public class ReportLogger : ReportLogger where T:IReportRecord, new() + /// The type of the report this logger will log. + public class ReportLogger : ReportLogger where T : IReportRecord, new() { - public ReportLogger(string fileName, AnalysisLogger logger) : base(fileName, logger) + public ReportLogger(string fileName, AnalysisLogger logger) + : base(fileName, logger) { Decorator = Decorator.Create(); } @@ -71,7 +71,7 @@ public ReportLogger(string fileName, AnalysisLogger logger) : base(fileName, log /// public void LogRecord(T record) { - Decorator.Apply(record); + Decorator.Apply(record); _records.Add(record); } From b1e1f54a33184d109318de641c7d01892348ebe2 Mon Sep 17 00:00:00 2001 From: markcowl Date: Mon, 1 Feb 2016 12:46:25 -0800 Subject: [PATCH 3/6] adding null checks to AssemblyMetadata and Decorator constructors --- tools/StaticAnalysis/Decorator.cs | 10 ++++++++++ .../DependencyAnalyzer/AssemblyMetadata.cs | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/tools/StaticAnalysis/Decorator.cs b/tools/StaticAnalysis/Decorator.cs index 64dc281e26fe..48ce7600f477 100644 --- a/tools/StaticAnalysis/Decorator.cs +++ b/tools/StaticAnalysis/Decorator.cs @@ -48,6 +48,16 @@ public void Apply(T record) public void AddDecorator(Action action, string name) { + if (action == null) + { + throw new ArgumentNullException("action"); + } + + if (name == null) + { + throw new ArgumentNullException("name"); + } + if (Inner == null) { Inner = new Decorator(action, name); diff --git a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyMetadata.cs b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyMetadata.cs index 6e3abe15e585..101cd1d99a2a 100644 --- a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyMetadata.cs +++ b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyMetadata.cs @@ -30,6 +30,11 @@ public class AssemblyMetadata public AssemblyMetadata(Assembly assembly) { + if (assembly == null) + { + throw new ArgumentNullException("assembly"); + } + _name = assembly.GetName(); _location = assembly.Location; _references = new List(); From e5f95e7914779dfada2e15160af7fb09a33562b0 Mon Sep 17 00:00:00 2001 From: markcowl Date: Mon, 1 Feb 2016 12:47:39 -0800 Subject: [PATCH 4/6] Adding header to assemblyinfo --- tools/StaticAnalysis/Properties/AssemblyInfo.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tools/StaticAnalysis/Properties/AssemblyInfo.cs b/tools/StaticAnalysis/Properties/AssemblyInfo.cs index 2dffc52f6682..8ae4b806c636 100644 --- a/tools/StaticAnalysis/Properties/AssemblyInfo.cs +++ b/tools/StaticAnalysis/Properties/AssemblyInfo.cs @@ -1,4 +1,18 @@ -using System.Reflection; +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; From f03c2efdbfd086419fe66532e27f00da935777e8 Mon Sep 17 00:00:00 2001 From: markcowl Date: Mon, 1 Feb 2016 12:51:12 -0800 Subject: [PATCH 5/6] fixing line endings --- .../DependencyAnalyzer/DependencyAnalyzer.cs | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/tools/StaticAnalysis/DependencyAnalyzer/DependencyAnalyzer.cs b/tools/StaticAnalysis/DependencyAnalyzer/DependencyAnalyzer.cs index aac335ee1f7f..829b78e3e343 100644 --- a/tools/StaticAnalysis/DependencyAnalyzer/DependencyAnalyzer.cs +++ b/tools/StaticAnalysis/DependencyAnalyzer/DependencyAnalyzer.cs @@ -246,8 +246,8 @@ private static bool IsCommandAssembly(AssemblyRecord assembly) private void FindExtraAssemblies() { - if (_assemblies.Values.Any(a => !IsCommandAssembly(a) && (a.ReferencingAssembly == null || a.ReferencingAssembly.Count == 0 || - !a.GetAncestors().Any(IsCommandAssembly)))) + if (_assemblies.Values.Any(a => !IsCommandAssembly(a) && (a.ReferencingAssembly == null + || a.ReferencingAssembly.Count == 0 || !a.GetAncestors().Any(IsCommandAssembly)))) { foreach ( var assembly in @@ -258,8 +258,10 @@ var assembly in { AssemblyName = assembly.Name, Severity = 2, - Description = string.Format("Assembly {0} is not referenced from any cmdlets assembly", assembly.Name), - Remediation = string.Format("Remove assembly {0} from the project and regenerate the Wix file", assembly.Name) + Description = string.Format("Assembly {0} is not referenced from any cmdlets assembly", + assembly.Name), + Remediation = string.Format("Remove assembly {0} from the project and regenerate the Wix " + + "file", assembly.Name) }); } } @@ -276,7 +278,8 @@ private void CheckAssemblyReference(AssemblyName reference, AssemblyRecord paren } else if (reference.Version.Major == 0 && reference.Version.Minor == 0) { - Logger.WriteWarning("{0}.dll has reference to assembly {1} without any version specification.", parent.Name, reference.Name); + Logger.WriteWarning("{0}.dll has reference to assembly {1} without any version specification.", + parent.Name, reference.Name); _versionConflictLogger.LogRecord(new AssemblyVersionConflict() { AssemblyName = reference.Name, @@ -284,10 +287,12 @@ private void CheckAssemblyReference(AssemblyName reference, AssemblyRecord paren ExpectedVersion = reference.Version, ParentAssembly = parent.Name, Severity = 2, - Description = string.Format("Assembly {0} referenced from {1}.dll does not specify any assembly version evidence. " + - "The assembly will use version {2} from disk.", reference.Name, parent.Name, stored.Version), - Remediation = string.Format("Update the reference to assembly {0} from {1} so that assembly version evidence is " + - "supplied", reference.Name, parent.Name) + Description = string.Format("Assembly {0} referenced from {1}.dll does not specify any " + + "assembly version evidence. The assembly will use version " + + "{2} from disk.", reference.Name, parent.Name, stored.Version), + Remediation = string.Format("Update the reference to assembly {0} from {1} so that " + + "assembly version evidence is supplied", reference.Name, + parent.Name) }); } else @@ -300,9 +305,11 @@ private void CheckAssemblyReference(AssemblyName reference, AssemblyRecord paren ExpectedVersion = reference.Version, ParentAssembly = parent.Name, Severity = 1, - Description = string.Format("Assembly {0} version {1} referenced from {2}.dll does not match assembly version on " + - "disk: {3}", reference.Name, reference.Version, parent.Name, stored.Version), - Remediation = string.Format("Update any references to version {0} of assembly {1}", minVersion, reference.Name) + Description = string.Format("Assembly {0} version {1} referenced from {2}.dll does " + + "not match assembly version on disk: {3}", + reference.Name, reference.Version, parent.Name, stored.Version), + Remediation = string.Format("Update any references to version {0} of assembly {1}", + minVersion, reference.Name) }); } } @@ -314,7 +321,8 @@ private void CheckAssemblyReference(AssemblyName reference, AssemblyRecord paren AssemblyVersion = reference.Version.ToString(), ReferencingAssembly = parent.Name, Severity = 0, - Description = string.Format("Missing assembly {0} referenced from {1}", reference.Name, parent.Name), + Description = string.Format("Missing assembly {0} referenced from {1}", reference.Name, + parent.Name), Remediation = "Ensure that the assembly is included in the Wix file or directory" }); } From 0817d6dfb173e67a3e8a6376ad17337096189e8d Mon Sep 17 00:00:00 2001 From: markcowl Date: Mon, 1 Feb 2016 12:54:07 -0800 Subject: [PATCH 6/6] More line endings fixes --- tools/StaticAnalysis/AnalysisLogger.cs | 3 ++- tools/StaticAnalysis/DependencyAnalyzer/AssemblyRecord.cs | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/StaticAnalysis/AnalysisLogger.cs b/tools/StaticAnalysis/AnalysisLogger.cs index 9c7091565e9e..f6aa7b493f3e 100644 --- a/tools/StaticAnalysis/AnalysisLogger.cs +++ b/tools/StaticAnalysis/AnalysisLogger.cs @@ -84,7 +84,8 @@ public virtual void WriteWarning(string format, params object[] args) /// /// The type of records written to the log /// The filename (without file path) where the report will be written - /// The given logger. Analyzer may write records to this logger and they will be written to the report file. + /// The given logger. Analyzer may write records to this logger and they will be written to + /// the report file. public virtual ReportLogger CreateLogger(string fileName) where T : IReportRecord, new() { if (string.IsNullOrWhiteSpace(fileName)) diff --git a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyRecord.cs b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyRecord.cs index cc02a9d08de5..a6a0c41e9e53 100644 --- a/tools/StaticAnalysis/DependencyAnalyzer/AssemblyRecord.cs +++ b/tools/StaticAnalysis/DependencyAnalyzer/AssemblyRecord.cs @@ -46,8 +46,8 @@ public class AssemblyRecord : ICloneable public int AssemblyFileMajorVersion { get; set; } /// - /// The minorVersion portion of the file version for the assembly file. This may or may not match the corresponding part of - /// the assembly version. + /// The minorVersion portion of the file version for the assembly file. This may or may not match the corresponding + /// part of the assembly version. /// public int AssemblyFileMinorVersion { get; set; } @@ -103,8 +103,8 @@ public bool Equals(AssemblyRecord record) public override string ToString() { StringBuilder output = new StringBuilder(); - output.AppendLine(string.Format("AssemblyName: {0}, Version:{1}, FileVersion: {2}.{3}, Location:{4}", Name, Version, - AssemblyFileMajorVersion, AssemblyFileMinorVersion, Location)); + output.AppendLine(string.Format("AssemblyName: {0}, Version:{1}, FileVersion: {2}.{3}, Location:{4}", + Name, Version, AssemblyFileMajorVersion, AssemblyFileMinorVersion, Location)); if (ReferencingAssembly.Any()) { output.AppendFormat("-> Parents: ({0})", string.Join(", ", ReferencingAssembly));