Created
April 22, 2015 09:54
-
-
Save SteveGilham/492f85160cfb74e1502d to your computer and use it in GitHub Desktop.
Computing cyclomatic complexity with PowerShell and FxCop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
This script estimates cyclomatic complexities over a folder full of assemblies. | |
.DESCRIPTION | |
It loads the assemblies, and then introspeccts over each method, estimating the | |
complexity as number of branch instructions which do not target the next instruction | |
and which have a unique target instruction. | |
.NOTES | |
File Name : Get-Complexity.ps1 | |
Requires : PowerShell Version 2.0 (3.0 or alternative launcher for .net 4/FxCop 10.0) | |
.PARAMETER FxCopPath | |
The path to the directory where FxCop has been installed | |
.PARAMETER AssemblyPath | |
The path to the folder containging the assemblies to inspect | |
.PARAMETER ReportLevel | |
If given, print out methods matching or exceeding this complexity, | |
otherwise return the whole analysis to the pipeline | |
#> | |
param ( | |
[string] $FxCopPath, | |
[Parameter(Mandatory = $true)] [string] $AssemblyPath, | |
[int] $ReportLevel, | |
[switch] $Help) | |
$fxcopVersion = @{ "v2"='Microsoft FxCop 1.36'; "v4"='Microsoft FxCop 10.0' } | |
if (-not $FxCopPath) { | |
$programFiles = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::ProgramFiles) | |
$dotNetVersion = [System.Runtime.InteropServices.RuntimeEnvironment]::GetSystemVersion().Split('.') | Select-Object -First 1 | |
$FxCopPath = Join-Path -Path $programFiles -ChildPath ($fxcopVersion[$dotNetVersion]) | |
} | |
if ($help -or (-not (Test-Path $FxCopPath -PathType Container)) -or | |
(-not (Test-Path $AssemblyPath -PathType Container)) -or | |
(-not (Test-Path $(Join-Path -Path $FxCopPath -ChildPath 'FxCopSdk.dll') -PathType Leaf))) | |
{ | |
Get-Help $MyInvocation.MyCommand.Definition | |
return | |
} | |
Add-Type -Path $(Join-Path -Path $FxCopPath -ChildPath 'FxCopSdk.dll') | |
## IL branch opcodes | |
$branchOpCodes = @([Microsoft.FxCop.Sdk.OpCode]::Beq, | |
[Microsoft.FxCop.Sdk.OpCode]::Beq_S, | |
[Microsoft.FxCop.Sdk.OpCode]::Bge, | |
[Microsoft.FxCop.Sdk.OpCode]::Bge_S, | |
[Microsoft.FxCop.Sdk.OpCode]::Bge_Un, | |
[Microsoft.FxCop.Sdk.OpCode]::Bge_Un_S, | |
[Microsoft.FxCop.Sdk.OpCode]::Bgt, | |
[Microsoft.FxCop.Sdk.OpCode]::Bgt_S, | |
[Microsoft.FxCop.Sdk.OpCode]::Bgt_Un, | |
[Microsoft.FxCop.Sdk.OpCode]::Bgt_Un_S, | |
[Microsoft.FxCop.Sdk.OpCode]::Ble, | |
[Microsoft.FxCop.Sdk.OpCode]::Ble_S, | |
[Microsoft.FxCop.Sdk.OpCode]::Ble_Un, | |
[Microsoft.FxCop.Sdk.OpCode]::Ble_Un_S, | |
[Microsoft.FxCop.Sdk.OpCode]::Blt, | |
[Microsoft.FxCop.Sdk.OpCode]::Blt_S, | |
[Microsoft.FxCop.Sdk.OpCode]::Blt_Un, | |
[Microsoft.FxCop.Sdk.OpCode]::Blt_Un_S, | |
[Microsoft.FxCop.Sdk.OpCode]::Bne_Un, | |
[Microsoft.FxCop.Sdk.OpCode]::Bne_Un_S, | |
[Microsoft.FxCop.Sdk.OpCode]::Br, | |
[Microsoft.FxCop.Sdk.OpCode]::Br_S, | |
[Microsoft.FxCop.Sdk.OpCode]::Brtrue, | |
[Microsoft.FxCop.Sdk.OpCode]::Brtrue_S, | |
[Microsoft.FxCop.Sdk.OpCode]::Brfalse, | |
[Microsoft.FxCop.Sdk.OpCode]::Brfalse_S) | |
# Compute method complexity (based on the algorithm of NDepend 1.3.2 by Patrick Smacchia) | |
Function Compute-Complexity ([Microsoft.FxCop.Sdk.InstructionCollection] $body) | |
{ | |
$offsets = @() | |
$body | ? { $branchOpCodes -contains $_.OpCode } | ? { | |
$_.Value -ne ($_.Offset + 2) } | ? { ## don't count branch to next instruction | |
-not ($offsets -contains $_.Offset) } | % { | |
$offsets += $_.Offset } | Out-Null | |
$offsets.Length | |
} | |
# load assemblies (no symbols needed) and explore methods | |
$assemblies = dir $AssemblyPath | ? { $_.Name.EndsWith(".exe") -or $_.Name.EndsWith(".dll") } | % { | |
$assembly = [Microsoft.FxCop.Sdk.AssemblyNode]::GetAssembly($_.FullName) | |
$compilerGenerated = $assembly.GetType([Microsoft.FxCop.Sdk.Identifier]::For("System.Runtime.CompilerServices"), [Microsoft.FxCop.Sdk.Identifier]::For("CompilerGeneratedAttribute"), $true) | |
New-Object PSObject -Property @{ | |
Name = $_.Name; | |
Types = $assembly.Types | % { | |
New-Object PSObject -Property @{ | |
Name = $_.FullName; | |
Methods = $_.Members | ? { $_.NodeType -eq [Microsoft.FxCop.Sdk.NodeType]::Method } | ? { | |
-not (($_.Attributes | % { $_.Type }) -contains $compilerGenerated) } | % { ## skip compiler generated code | |
New-Object PSObject -Property @{ | |
Name = $_.GetUnmangledNameWithTypeParameters(); | |
Complexity = Compute-Complexity($_.Instructions); }}}}}} | |
if ($ReportLevel) { | |
$assemblies | % { $aname = $_.Name | |
$_.Types | % { $cname = "`t$($_.Name)" | |
$_.Methods | ? { $_.Complexity -ge $ReportLevel } | % { | |
if ($aname) { Write-Host $aname; $aname = $null } | |
if ($cname) { Write-Host $cname; $cname = $null } | |
Write-Host "`t`t$_" }}}} else { $assemblies } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment