Skip to content

Make the build fail for new StaticAnalysis issues #1883

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AzurePowershell.Test.targets
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
<AsmXUnitTests Include=".\src\ServiceManagement\Network\Commands.Network.Test\bin\Debug\Microsoft.WindowsAzure.Commands.ServiceManagement.Network.Test.dll"/>
</ItemGroup>
<ItemGroup Condition=" '$(scope)' == 'all' ">
<XUnitTests Include=".\tools\StaticAnalysis\StaticAnalysis.Test\bin\Debug\StaticAnalysis.Test.dll"/>
<XUnitTests Include=".\src\ResourceManager\SiteRecovery\Commands.SiteRecovery.Test\bin\Debug\Microsoft.Azure.Commands.SiteRecovery.Test.dll"/>
<XUnitTests Include=".\src\ResourceManager\Sql\Commands.Sql.Test\bin\Debug\Microsoft.Azure.Commands.Sql.Test.dll"/>
<XUnitTests Include=".\src\ResourceManager\Resources\Commands.Resources.Test\bin\Debug\Microsoft.Azure.Commands.Resources.Test.dll"/>
Expand Down
3 changes: 3 additions & 0 deletions build.proj
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@
<!-- Restore packages -->
<Exec Command="$(NuGetCommand) restore %(CmdletSolutionsToBuild.FullPath) $(NuGetRestoreConfigSwitch)"
ContinueOnError="false" />
<!-- Restore packages for static analysis-->
<Exec Command="$(NuGetCommand) restore %(StaticAnalysis.FullPath) $(NuGetRestoreConfigSwitch)"
ContinueOnError="false" />

<!--Restore the xunit runner needed to run unit tests-->
<Exec Command="$(NuGetCommand) restore $(MSBuildProjectDirectory)\packages.config -PackagesDirectory $(MSBuildProjectDirectory)\packages" />
Expand Down
57 changes: 54 additions & 3 deletions tools/StaticAnalysis/AnalysisLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,27 @@ namespace StaticAnalysis
public abstract class AnalysisLogger
{
string _baseDirectory;
string _exceptionsDirectory;

/// <summary>
/// Create an analysis logger that will write reports to the given directory
/// </summary>
/// <param name="baseDirectory"></param>
/// <param name="baseDirectory">The base directory for reports</param>
/// <param name="exceptionsDirectory">The directory containing exceptions form static analysis rules.</param>
public AnalysisLogger(string baseDirectory, string exceptionsDirectory)
{
_baseDirectory = baseDirectory;
_exceptionsDirectory = exceptionsDirectory;
}

/// <summary>
/// Create an analysis logger without exceptions
/// </summary>
/// <param name="baseDirectory">The base directory for reports</param>
public AnalysisLogger(string baseDirectory)
{
_baseDirectory = baseDirectory;
_exceptionsDirectory = null;
}

IList<ReportLogger> _loggers = new List<ReportLogger>();
Expand Down Expand Up @@ -86,15 +99,27 @@ public virtual void WriteWarning(string format, params object[] args)
/// <param name="fileName">The filename (without file path) where the report will be written</param>
/// <returns>The given logger. Analyzer may write records to this logger and they will be written to
/// the report file.</returns>
public virtual ReportLogger<T> CreateLogger<T>(string fileName) where T : IReportRecord, new()
public virtual ReportLogger<T> CreateLogger<T>(string fileName) where T : class, IReportRecord, new()
{
if (string.IsNullOrWhiteSpace(fileName))
{
throw new ArgumentNullException("fileName");
}

var filePath = Path.Combine(_baseDirectory, fileName);
var logger = new ReportLogger<T>(filePath, this);
ReportLogger<T> logger;
if (_exceptionsDirectory != null && Directory.Exists(_exceptionsDirectory))
{
var exceptionsPath = Path.Combine(_exceptionsDirectory, fileName);
WriteWarning("Using exceptions file {0}", exceptionsPath);
logger = new ReportLogger<T>(filePath, exceptionsPath, this);
}
else
{
WriteWarning("Using no exceptions file.");
logger = new ReportLogger<T>(filePath, this);
}

Loggers.Add(logger);
return logger;
}
Expand All @@ -116,5 +141,31 @@ public void WriteReports()
WriteReport(logger.FileName, reportText.ToString());
}
}

public void CheckForIssues(int maxSeverity)
{
var hasErrors = false;
foreach (var logger in Loggers.Where(l => l.Records.Any(r => r.Severity < maxSeverity)))
{
hasErrors = true;
StringBuilder errorText = new StringBuilder();
errorText.AppendLine(logger.Records.First().PrintHeaders());
foreach (var reportRecord in logger.Records)
{
errorText.AppendLine(reportRecord.FormatRecord());
}

WriteError("{0} Errors", logger.FileName);
WriteError(errorText.ToString());
WriteError("");
}

if (hasErrors)
{
throw new InvalidOperationException(string.Format("One or more errors occurred in validation. " +
"See the analysis repots at {0} for details",
_baseDirectory));
}
}
}
}
6 changes: 5 additions & 1 deletion tools/StaticAnalysis/ConsoleLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@ namespace StaticAnalysis
public class ConsoleLogger : AnalysisLogger
{

public ConsoleLogger(string baseDirectory, string exceptionsDirectory)
: base(baseDirectory, exceptionsDirectory)
{
}

public ConsoleLogger(string baseDirectory)
: base(baseDirectory)
{
}

public override void WriteError(string error)
{
Console.WriteLine("### ERROR {0}", error);
Expand Down
6 changes: 6 additions & 0 deletions tools/StaticAnalysis/DependencyAnalyzer/AssemblyRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ public override string ToString()

}

public override int GetHashCode()
{
return string.Format("{0}-{1}-{2}", AssemblyName, AssemblyFileMajorVersion, AssemblyFileMinorVersion).GetHashCode();
}


/// <summary>
/// Get all the ancestors in the ancestor tree
/// </summary>
Expand Down
49 changes: 46 additions & 3 deletions tools/StaticAnalysis/DependencyAnalyzer/AssemblyVersionConflict.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
// ----------------------------------------------------------------------------------

using System;
using System.IO;
using System.Text.RegularExpressions;

namespace StaticAnalysis.DependencyAnalyzer
{
Expand Down Expand Up @@ -46,6 +48,11 @@ public class AssemblyVersionConflict : IReportRecord
/// </summary>
public string ParentAssembly { get; set; }

/// <summary>
/// Machine readable identity of the problem
/// </summary>
public int ProblemId { get; set; }

/// <summary>
/// A textual description of the problem
/// </summary>
Expand All @@ -62,16 +69,52 @@ public string PrintHeaders()
{
return
"\"Directory\",\"AssemblyName\",\"Expected Version\",\"Actual Version\",\"Parent Assembly\",\"Severity\"," +
"\"Description\",\"Remediation\"";
"\"ProblemId\",\"Description\",\"Remediation\"";
}

public string FormatRecord()
{
return
string.Format(
"\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\"",
"\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\"",
Directory, AssemblyName, ExpectedVersion, ActualVersion, ParentAssembly, Severity,
Description, Remediation);
ProblemId, Description, Remediation);
}
public bool Match(IReportRecord other)
{
var result = false;
var record = other as AssemblyVersionConflict;
if (record != null)
{
result = string.Equals(EnvironmentHelpers.GetDirectoryName(record.Directory),
EnvironmentHelpers.GetDirectoryName(Directory), StringComparison.OrdinalIgnoreCase)
&& string.Equals(record.AssemblyName, AssemblyName, StringComparison.OrdinalIgnoreCase)
&& string.Equals(record.ParentAssembly, ParentAssembly, StringComparison.OrdinalIgnoreCase)
&&record.ProblemId == ProblemId;
}

return result;
}

public IReportRecord Parse(string line)
{
var matcher = "\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\"";
var match = Regex.Match(line, matcher);
if (!match.Success || match.Groups.Count < 10)
{
throw new InvalidOperationException(string.Format("Could not parse '{0}' as AssemblyVersionConflict record", line));
}

Directory = match.Groups[1].Value;
AssemblyName = match.Groups[2].Value;
ExpectedVersion = Version.Parse(match.Groups[3].Value);
ActualVersion = Version.Parse(match.Groups[4].Value);
ParentAssembly = match.Groups[5].Value;
Severity = int.Parse(match.Groups[6].Value);
ProblemId = int.Parse(match.Groups[7].Value);
Description = match.Groups[8].Value;
Remediation = match.Groups[9].Value;
return this;
}

public override string ToString()
Expand Down
27 changes: 25 additions & 2 deletions tools/StaticAnalysis/DependencyAnalyzer/DependencyAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// ----------------------------------------------------------------------------------

using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
Expand All @@ -26,6 +27,21 @@ namespace StaticAnalysis.DependencyAnalyzer
/// </summary>
public class DependencyAnalyzer : IStaticAnalyzer
{
const int NoAssemblyVersionEvidence = 1000;
const int ReferenceDoesNotMatchAssemblyVersion = 1010;
const int ExtraAssemblyRecord = 2000;
const int MissingAssemblyRecord = 3000;
const int AssemblyVersionFileVersionMismatch = 7000;
const int CommonAuthenticationMismatch = 7010;

static List<string> FrameworkAssemblies = new List<string>
{
"Microsoft.CSharp",
"Microsoft.Management.Infrastructure",
"Microsoft.Build",
"Microsoft.Build.Framework"
};

private Dictionary<string, AssemblyRecord> _assemblies =
new Dictionary<string, AssemblyRecord>(StringComparer.OrdinalIgnoreCase);
private Dictionary<AssemblyName, AssemblyRecord> _sharedAssemblyReferences =
Expand Down Expand Up @@ -130,6 +146,7 @@ private bool AddSharedAssembly(AssemblyRecord assembly)
},
AssemblyVersion = assembly.Version,
Severity = 0,
ProblemId = AssemblyVersionFileVersionMismatch,
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 " +
Expand Down Expand Up @@ -170,6 +187,7 @@ private bool AddSharedAssemblyExactVersion(AssemblyRecord record)
AssemblyName = record.Name,
AssemblyVersion = record.Version,
Severity = 0,
ProblemId = CommonAuthenticationMismatch,
AssemblyPathsAndFileVersions = new List<Tuple<string, Version>>()
{
new Tuple<string, Version>(record.Location, new Version(record.AssemblyFileMajorVersion,
Expand Down Expand Up @@ -202,14 +220,15 @@ private static bool RequiresExactVersionMatch(AssemblyRecord name)

private static bool IsFrameworkAssembly(AssemblyName name)
{
return name.Name.StartsWith("System") || name.Name.Equals("mscorlib");
return name.Name.StartsWith("System") || name.Name.Equals("mscorlib")
|| FrameworkAssemblies.Contains(name.Name);
}

private void ProcessDirectory(string directoryPath)
{
var savedDirectory = Directory.GetCurrentDirectory();
Directory.SetCurrentDirectory(directoryPath);
_loader = AppDomainHelpers.CreateProxy<AssemblyLoader>(directoryPath, out _testDomain);
_loader = EnvironmentHelpers.CreateProxy<AssemblyLoader>(directoryPath, out _testDomain);
foreach (var file in Directory.GetFiles(directoryPath).Where(file => file.EndsWith(".dll")))
{
AssemblyRecord assembly = CreateAssemblyRecord(file);
Expand Down Expand Up @@ -258,6 +277,7 @@ var assembly in
{
AssemblyName = assembly.Name,
Severity = 2,
ProblemId = ExtraAssemblyRecord,
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 " +
Expand Down Expand Up @@ -286,6 +306,7 @@ private void CheckAssemblyReference(AssemblyName reference, AssemblyRecord paren
ActualVersion = stored.Version,
ExpectedVersion = reference.Version,
ParentAssembly = parent.Name,
ProblemId = NoAssemblyVersionEvidence,
Severity = 2,
Description = string.Format("Assembly {0} referenced from {1}.dll does not specify any " +
"assembly version evidence. The assembly will use version " +
Expand All @@ -304,6 +325,7 @@ private void CheckAssemblyReference(AssemblyName reference, AssemblyRecord paren
ActualVersion = stored.Version,
ExpectedVersion = reference.Version,
ParentAssembly = parent.Name,
ProblemId = ReferenceDoesNotMatchAssemblyVersion,
Severity = 1,
Description = string.Format("Assembly {0} version {1} referenced from {2}.dll does " +
"not match assembly version on disk: {3}",
Expand All @@ -321,6 +343,7 @@ private void CheckAssemblyReference(AssemblyName reference, AssemblyRecord paren
AssemblyVersion = reference.Version.ToString(),
ReferencingAssembly = parent.Name,
Severity = 0,
ProblemId = MissingAssemblyRecord,
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"
Expand Down
45 changes: 42 additions & 3 deletions tools/StaticAnalysis/DependencyAnalyzer/ExtraAssembly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
// limitations under the License.
// ----------------------------------------------------------------------------------

using System;
using System.Text.RegularExpressions;

namespace StaticAnalysis.DependencyAnalyzer
{
/// <summary>
Expand All @@ -25,25 +28,61 @@ public class ExtraAssembly : IReportRecord

public int Severity { get; set; }

public int ProblemId { get; set; }

public string Description { get; set; }

public string Remediation { get; set; }


public string PrintHeaders()
{
return "\"Directory\",\"AssemblyName\",\"Severity\",\"Description\",\"Remediation\"";
return "\"Directory\",\"AssemblyName\",\"Severity\",\"ProblemId\",\"Description\",\"Remediation\"";
}

public string FormatRecord()
{
return string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\"",
Directory, AssemblyName, Severity, Description, Remediation);
return string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\"",
Directory, AssemblyName, Severity, ProblemId, Description, Remediation);
}

public override string ToString()
{
return FormatRecord();
}


public bool Match(IReportRecord other)
{
var result = false;
var record = other as ExtraAssembly;
if (record != null)
{
result = string.Equals(EnvironmentHelpers.GetDirectoryName(record.Directory),
EnvironmentHelpers.GetDirectoryName(Directory), StringComparison.OrdinalIgnoreCase)
&& string.Equals(record.AssemblyName, AssemblyName, StringComparison.OrdinalIgnoreCase)
&& record.ProblemId == ProblemId;
}

return result;
}

public IReportRecord Parse(string line)
{
var matcher = "\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\"";
var match = Regex.Match(line, matcher);
if (!match.Success || match.Groups.Count < 7)
{
throw new InvalidOperationException(string.Format("Could not parse '{0}' as ExtraAssembly record", line));
}

Directory = match.Groups[1].Value;
AssemblyName = match.Groups[2].Value;
Severity = int.Parse(match.Groups[3].Value);
ProblemId = int.Parse(match.Groups[4].Value);
Description = match.Groups[5].Value;
Remediation = match.Groups[6].Value;
return this;
}
}
}
Loading