CyclomaticComplexity.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using System.Reflection.Emit;
  5. using Mono.Reflection;
  6. namespace UnityEditor.TestTools.CodeCoverage.OpenCover
  7. {
  8. internal static class CyclomaticComplexity
  9. {
  10. private static List<Instruction> targets = new List<Instruction>();
  11. public static int CalculateCyclomaticComplexity(this MethodBase method)
  12. {
  13. if (method == null || method.GetMethodBody() == null)
  14. {
  15. return 1;
  16. }
  17. bool hasSwitch = false;
  18. foreach (Instruction ins in method.GetInstructions())
  19. {
  20. if (ins.OpCode.OperandType == OperandType.InlineSwitch)
  21. {
  22. hasSwitch = true;
  23. break;
  24. }
  25. }
  26. if (hasSwitch)
  27. {
  28. return GetSwitchCyclomaticComplexity(method);
  29. }
  30. return GetFastCyclomaticComplexity(method);
  31. }
  32. private static int GetFastCyclomaticComplexity(MethodBase method)
  33. {
  34. int cc = 1;
  35. foreach (Instruction ins in method.GetInstructions())
  36. {
  37. switch (ins.OpCode.FlowControl)
  38. {
  39. case FlowControl.Branch:
  40. // detect ternary pattern
  41. Instruction previous = ins.Previous;
  42. if (previous != null && previous.OpCode.Name.StartsWith("ld"))
  43. {
  44. ++cc;
  45. }
  46. break;
  47. case FlowControl.Cond_Branch:
  48. ++cc;
  49. break;
  50. }
  51. }
  52. return cc;
  53. }
  54. private static int GetSwitchCyclomaticComplexity(MethodBase method)
  55. {
  56. Instruction previous = null;
  57. Instruction branch = null;
  58. int cc = 1;
  59. foreach (Instruction ins in method.GetInstructions())
  60. {
  61. switch (ins.OpCode.FlowControl)
  62. {
  63. case FlowControl.Branch:
  64. if (previous == null)
  65. {
  66. continue;
  67. }
  68. // detect ternary pattern
  69. previous = ins.Previous;
  70. if (previous.OpCode.Name.StartsWith("ld"))
  71. {
  72. cc++;
  73. }
  74. // or 'default' (xmcs)
  75. if (previous.OpCode.FlowControl == FlowControl.Cond_Branch)
  76. {
  77. branch = (previous.Operand as Instruction);
  78. // branch can be null (e.g. switch -> Instruction[])
  79. if ((branch != null) && targets.Contains(branch) && !targets.Contains(ins))
  80. {
  81. targets.Add(ins);
  82. }
  83. }
  84. break;
  85. case FlowControl.Cond_Branch:
  86. // note: a single switch (C#) with sparse values can be broken into several swicth (IL)
  87. // that will use the same 'targets' and must be counted only once
  88. if (ins.OpCode.OperandType == OperandType.InlineSwitch)
  89. {
  90. AccumulateSwitchTargets(ins);
  91. }
  92. else
  93. {
  94. // some conditional branch can be related to the sparse switch
  95. branch = (ins.Operand as Instruction);
  96. previous = branch.Previous;
  97. if ((previous != null) && previous.Previous.OpCode.OperandType != OperandType.InlineSwitch)
  98. {
  99. if (!targets.Contains(branch))
  100. {
  101. cc++;
  102. }
  103. }
  104. }
  105. break;
  106. }
  107. }
  108. // count all unique targets (and default if more than one C# switch is used)
  109. cc += targets.Count;
  110. targets.Clear();
  111. return cc;
  112. }
  113. private static void AccumulateSwitchTargets(Instruction ins)
  114. {
  115. Instruction[] cases = (Instruction[])ins.Operand;
  116. foreach (Instruction target in cases)
  117. {
  118. // ignore targets that are the next instructions (xmcs)
  119. if (target != ins.Next && !targets.Contains(target))
  120. targets.Add(target);
  121. }
  122. // add 'default' branch (if one exists)
  123. Instruction next = ins.Next;
  124. if (next.OpCode.FlowControl == FlowControl.Branch)
  125. {
  126. Instruction unc = FindFirstUnconditionalBranchTarget(cases[0]);
  127. if (unc != next.Operand && !targets.Contains(next.Operand as Instruction))
  128. targets.Add(next.Operand as Instruction);
  129. }
  130. }
  131. private static Instruction FindFirstUnconditionalBranchTarget(Instruction ins)
  132. {
  133. while (ins != null)
  134. {
  135. if (FlowControl.Branch == ins.OpCode.FlowControl)
  136. return ((Instruction)ins.Operand);
  137. ins = ins.Next;
  138. }
  139. return null;
  140. }
  141. }
  142. }