Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Computing cyclomatic complexity with PowerShell and FxCop
<#
.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