158 lines
5.5 KiB
C#
158 lines
5.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using Mono.Reflection;
|
|
|
|
namespace UnityEditor.TestTools.CodeCoverage.OpenCover
|
|
{
|
|
internal static class CyclomaticComplexity
|
|
{
|
|
private static List<Instruction> targets = new List<Instruction>();
|
|
|
|
public static int CalculateCyclomaticComplexity(this MethodBase method)
|
|
{
|
|
if (method == null || method.GetMethodBody() == null)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
bool hasSwitch = false;
|
|
foreach (Instruction ins in method.GetInstructions())
|
|
{
|
|
if (ins.OpCode.OperandType == OperandType.InlineSwitch)
|
|
{
|
|
hasSwitch = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hasSwitch)
|
|
{
|
|
return GetSwitchCyclomaticComplexity(method);
|
|
}
|
|
return GetFastCyclomaticComplexity(method);
|
|
}
|
|
|
|
private static int GetFastCyclomaticComplexity(MethodBase method)
|
|
{
|
|
int cc = 1;
|
|
foreach (Instruction ins in method.GetInstructions())
|
|
{
|
|
switch (ins.OpCode.FlowControl)
|
|
{
|
|
case FlowControl.Branch:
|
|
// detect ternary pattern
|
|
Instruction previous = ins.Previous;
|
|
if (previous != null && previous.OpCode.Name.StartsWith("ld"))
|
|
{
|
|
++cc;
|
|
}
|
|
break;
|
|
|
|
case FlowControl.Cond_Branch:
|
|
++cc;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return cc;
|
|
}
|
|
|
|
private static int GetSwitchCyclomaticComplexity(MethodBase method)
|
|
{
|
|
Instruction previous = null;
|
|
Instruction branch = null;
|
|
int cc = 1;
|
|
|
|
foreach (Instruction ins in method.GetInstructions())
|
|
{
|
|
switch (ins.OpCode.FlowControl)
|
|
{
|
|
case FlowControl.Branch:
|
|
if (previous == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// detect ternary pattern
|
|
previous = ins.Previous;
|
|
if (previous.OpCode.Name.StartsWith("ld"))
|
|
{
|
|
cc++;
|
|
}
|
|
|
|
// or 'default' (xmcs)
|
|
if (previous.OpCode.FlowControl == FlowControl.Cond_Branch)
|
|
{
|
|
branch = (previous.Operand as Instruction);
|
|
// branch can be null (e.g. switch -> Instruction[])
|
|
if ((branch != null) && targets.Contains(branch) && !targets.Contains(ins))
|
|
{
|
|
targets.Add(ins);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case FlowControl.Cond_Branch:
|
|
// note: a single switch (C#) with sparse values can be broken into several swicth (IL)
|
|
// that will use the same 'targets' and must be counted only once
|
|
if (ins.OpCode.OperandType == OperandType.InlineSwitch)
|
|
{
|
|
AccumulateSwitchTargets(ins);
|
|
}
|
|
else
|
|
{
|
|
// some conditional branch can be related to the sparse switch
|
|
branch = (ins.Operand as Instruction);
|
|
previous = branch.Previous;
|
|
if ((previous != null) && previous.Previous.OpCode.OperandType != OperandType.InlineSwitch)
|
|
{
|
|
if (!targets.Contains(branch))
|
|
{
|
|
cc++;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
// count all unique targets (and default if more than one C# switch is used)
|
|
cc += targets.Count;
|
|
targets.Clear();
|
|
|
|
return cc;
|
|
}
|
|
|
|
private static void AccumulateSwitchTargets(Instruction ins)
|
|
{
|
|
Instruction[] cases = (Instruction[])ins.Operand;
|
|
foreach (Instruction target in cases)
|
|
{
|
|
// ignore targets that are the next instructions (xmcs)
|
|
if (target != ins.Next && !targets.Contains(target))
|
|
targets.Add(target);
|
|
}
|
|
// add 'default' branch (if one exists)
|
|
Instruction next = ins.Next;
|
|
if (next.OpCode.FlowControl == FlowControl.Branch)
|
|
{
|
|
Instruction unc = FindFirstUnconditionalBranchTarget(cases[0]);
|
|
if (unc != next.Operand && !targets.Contains(next.Operand as Instruction))
|
|
targets.Add(next.Operand as Instruction);
|
|
}
|
|
}
|
|
|
|
private static Instruction FindFirstUnconditionalBranchTarget(Instruction ins)
|
|
{
|
|
while (ins != null)
|
|
{
|
|
if (FlowControl.Branch == ins.OpCode.FlowControl)
|
|
return ((Instruction)ins.Operand);
|
|
|
|
ins = ins.Next;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
}
|