You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

199 lines
7.7 KiB

  1. // Copyright (c) 2017 Siegfried Pammer
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Linq;
  21. using ICSharpCode.Decompiler.IL.ControlFlow;
  22. using ICSharpCode.Decompiler.TypeSystem;
  23. using ICSharpCode.Decompiler.Util;
  24. namespace ICSharpCode.Decompiler.IL.Transforms
  25. {
  26. /// <summary>
  27. /// Detects switch-on-nullable patterns employed by the C# compiler and transforms them to an ILAst-switch-instruction.
  28. /// </summary>
  29. public class SwitchOnNullableTransform : IILTransform
  30. {
  31. public void Run(ILFunction function, ILTransformContext context)
  32. {
  33. if (!context.Settings.LiftNullables)
  34. return;
  35. HashSet<BlockContainer> changedContainers = new HashSet<BlockContainer>();
  36. foreach (var block in function.Descendants.OfType<Block>())
  37. {
  38. bool changed = false;
  39. for (int i = block.Instructions.Count - 1; i >= 0; i--)
  40. {
  41. SwitchInstruction newSwitch;
  42. if (MatchSwitchOnNullable(block.Instructions, i, out newSwitch))
  43. {
  44. newSwitch.AddILRange(block.Instructions[i - 2]);
  45. block.Instructions[i + 1].ReplaceWith(newSwitch);
  46. block.Instructions.RemoveRange(i - 2, 3);
  47. i -= 2;
  48. changed = true;
  49. continue;
  50. }
  51. if (MatchRoslynSwitchOnNullable(block.Instructions, i, out newSwitch))
  52. {
  53. newSwitch.AddILRange(block.Instructions[i]);
  54. newSwitch.AddILRange(block.Instructions[i + 1]);
  55. block.Instructions[i].ReplaceWith(newSwitch);
  56. block.Instructions.RemoveAt(i + 1);
  57. changed = true;
  58. continue;
  59. }
  60. }
  61. if (!changed)
  62. continue;
  63. SwitchDetection.SimplifySwitchInstruction(block, context);
  64. if (block.Parent is BlockContainer container)
  65. changedContainers.Add(container);
  66. }
  67. foreach (var container in changedContainers)
  68. container.SortBlocks(deleteUnreachableBlocks: true);
  69. }
  70. /// <summary>
  71. /// Matches legacy C# switch on nullable.
  72. /// </summary>
  73. bool MatchSwitchOnNullable(InstructionCollection<ILInstruction> instructions, int i, out SwitchInstruction newSwitch)
  74. {
  75. newSwitch = null;
  76. // match first block:
  77. // stloc tmp(ldloca switchValueVar)
  78. // stloc switchVariable(call GetValueOrDefault(ldloc tmp))
  79. // if (logic.not(call get_HasValue(ldloc tmp))) br nullCaseBlock
  80. // br switchBlock
  81. if (i < 2)
  82. return false;
  83. if (!instructions[i - 2].MatchStLoc(out var tmp, out var ldloca) ||
  84. !instructions[i - 1].MatchStLoc(out var switchVariable, out var getValueOrDefault) ||
  85. !instructions[i].MatchIfInstruction(out var condition, out var trueInst))
  86. return false;
  87. if (!tmp.IsSingleDefinition || tmp.LoadCount != 2)
  88. return false;
  89. if (!switchVariable.IsSingleDefinition || switchVariable.LoadCount != 1)
  90. return false;
  91. if (!instructions[i + 1].MatchBranch(out var switchBlock) || !trueInst.MatchBranch(out var nullCaseBlock))
  92. return false;
  93. if (!ldloca.MatchLdLoca(out var switchValueVar))
  94. return false;
  95. if (!condition.MatchLogicNot(out var getHasValue))
  96. return false;
  97. if (!NullableLiftingTransform.MatchGetValueOrDefault(getValueOrDefault, out ILInstruction getValueOrDefaultArg))
  98. return false;
  99. if (!NullableLiftingTransform.MatchHasValueCall(getHasValue, out ILInstruction getHasValueArg))
  100. return false;
  101. if (!(getHasValueArg.MatchLdLoc(tmp) && getValueOrDefaultArg.MatchLdLoc(tmp)))
  102. return false;
  103. // match second block: switchBlock
  104. // switch (ldloc switchVariable) {
  105. // case [0..1): br caseBlock1
  106. // ... more cases ...
  107. // case [long.MinValue..0),[1..5),[6..10),[11..long.MaxValue]: br defaultBlock
  108. // }
  109. if (switchBlock.Instructions.Count != 1 || switchBlock.IncomingEdgeCount != 1)
  110. return false;
  111. if (!(switchBlock.Instructions[0] is SwitchInstruction switchInst))
  112. return false;
  113. newSwitch = BuildLiftedSwitch(nullCaseBlock, switchInst, new LdLoc(switchValueVar));
  114. return true;
  115. }
  116. static SwitchInstruction BuildLiftedSwitch(Block nullCaseBlock, SwitchInstruction switchInst, ILInstruction switchValue)
  117. {
  118. SwitchInstruction newSwitch = new SwitchInstruction(switchValue);
  119. newSwitch.IsLifted = true;
  120. newSwitch.Sections.AddRange(switchInst.Sections);
  121. newSwitch.Sections.Add(new SwitchSection { Body = new Branch(nullCaseBlock), HasNullLabel = true });
  122. return newSwitch;
  123. }
  124. /// <summary>
  125. /// Matches Roslyn C# switch on nullable.
  126. /// </summary>
  127. bool MatchRoslynSwitchOnNullable(InstructionCollection<ILInstruction> instructions, int i, out SwitchInstruction newSwitch)
  128. {
  129. newSwitch = null;
  130. // match first block:
  131. // if (logic.not(call get_HasValue(target))) br nullCaseBlock
  132. // br switchBlock
  133. if (!instructions[i].MatchIfInstruction(out var condition, out var trueInst))
  134. return false;
  135. if (!instructions[i + 1].MatchBranch(out var switchBlock) || !trueInst.MatchBranch(out var nullCaseBlock))
  136. return false;
  137. if (!condition.MatchLogicNot(out var getHasValue) || !NullableLiftingTransform.MatchHasValueCall(getHasValue, out ILInstruction target) || !SemanticHelper.IsPure(target.Flags))
  138. return false;
  139. // match second block: switchBlock
  140. // note: I have seen cases where switchVar is inlined into the switch.
  141. // stloc switchVar(call GetValueOrDefault(ldloca tmp))
  142. // switch (ldloc switchVar) {
  143. // case [0..1): br caseBlock1
  144. // ... more cases ...
  145. // case [long.MinValue..0),[1..5),[6..10),[11..long.MaxValue]: br defaultBlock
  146. // }
  147. if (switchBlock.IncomingEdgeCount != 1)
  148. return false;
  149. SwitchInstruction switchInst;
  150. switch (switchBlock.Instructions.Count)
  151. {
  152. case 2:
  153. {
  154. // this is the normal case described by the pattern above
  155. if (!switchBlock.Instructions[0].MatchStLoc(out var switchVar, out var getValueOrDefault))
  156. return false;
  157. if (!switchVar.IsSingleDefinition || switchVar.LoadCount != 1)
  158. return false;
  159. if (!(NullableLiftingTransform.MatchGetValueOrDefault(getValueOrDefault, out ILInstruction target2) && target2.Match(target).Success))
  160. return false;
  161. if (!(switchBlock.Instructions[1] is SwitchInstruction si))
  162. return false;
  163. switchInst = si;
  164. break;
  165. }
  166. case 1:
  167. {
  168. // this is the special case where `call GetValueOrDefault(ldloca tmp)` is inlined into the switch.
  169. if (!(switchBlock.Instructions[0] is SwitchInstruction si))
  170. return false;
  171. if (!(NullableLiftingTransform.MatchGetValueOrDefault(si.Value, out ILInstruction target2) && target2.Match(target).Success))
  172. return false;
  173. switchInst = si;
  174. break;
  175. }
  176. default:
  177. {
  178. return false;
  179. }
  180. }
  181. ILInstruction switchValue;
  182. if (target.MatchLdLoca(out var v))
  183. switchValue = new LdLoc(v).WithILRange(target);
  184. else
  185. switchValue = new LdObj(target, ((CallInstruction)getHasValue).Method.DeclaringType);
  186. newSwitch = BuildLiftedSwitch(nullCaseBlock, switchInst, switchValue);
  187. return true;
  188. }
  189. }
  190. }