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.

340 lines
12 KiB

  1. // Copyright (c) 2018 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.Collections.Generic;
  19. using System.Linq;
  20. using ICSharpCode.Decompiler.FlowAnalysis;
  21. using ICSharpCode.Decompiler.IL.Transforms;
  22. using ICSharpCode.Decompiler.TypeSystem;
  23. namespace ICSharpCode.Decompiler.IL.ControlFlow
  24. {
  25. class AwaitInFinallyTransform
  26. {
  27. public static void Run(ILFunction function, ILTransformContext context)
  28. {
  29. if (!context.Settings.AwaitInCatchFinally)
  30. return;
  31. HashSet<BlockContainer> changedContainers = new HashSet<BlockContainer>();
  32. // analyze all try-catch statements in the function
  33. foreach (var tryCatch in function.Descendants.OfType<TryCatch>().ToArray())
  34. {
  35. if (!(tryCatch.Parent?.Parent is BlockContainer container))
  36. continue;
  37. // } catch exceptionVariable : 02000078 System.Object when (ldc.i4 1) BlockContainer {
  38. // Block IL_004a (incoming: 1) {
  39. // stloc objectVariable(ldloc exceptionVariable)
  40. // br finallyBlock
  41. // }
  42. //
  43. // }
  44. // }
  45. //
  46. // Block finallyBlock (incoming: 2) {
  47. // if (comp.o(ldloc b == ldnull)) br afterFinallyBlock
  48. // br finallyBlockContinuation
  49. // }
  50. //
  51. // Block finallyBlockContinuation (incoming: 1) {
  52. // await(addressof System.Threading.Tasks.ValueTask(callvirt DisposeAsync(ldloc b)))
  53. // br afterFinallyBlock
  54. // }
  55. //
  56. // Block afterFinallyBlock (incoming: 2) {
  57. // stloc V_1(ldloc objectVariable)
  58. // if (comp.o(ldloc V_1 == ldnull)) br IL_00ea
  59. // br IL_00cf
  60. // }
  61. // await in finally uses a single catch block with catch-type object
  62. if (tryCatch.Handlers.Count != 1)
  63. {
  64. continue;
  65. }
  66. var handler = tryCatch.Handlers[0];
  67. var exceptionVariable = handler.Variable;
  68. if (handler.Body is not BlockContainer catchBlockContainer)
  69. continue;
  70. if (!exceptionVariable.Type.IsKnownType(KnownTypeCode.Object))
  71. continue;
  72. // Matches the await finally pattern:
  73. // [stloc V_3(ldloc E_100) - copy exception variable to a temporary]
  74. // stloc V_6(ldloc V_3) - store exception in 'global' object variable
  75. // br IL_0075 - jump out of catch block to the head of the finallyBlock
  76. var catchBlockEntry = catchBlockContainer.EntryPoint;
  77. ILVariable objectVariable;
  78. switch (catchBlockEntry.Instructions.Count)
  79. {
  80. case 2:
  81. if (!catchBlockEntry.Instructions[0].MatchStLoc(out objectVariable, out var value))
  82. continue;
  83. if (!value.MatchLdLoc(exceptionVariable))
  84. continue;
  85. break;
  86. case 3:
  87. if (!catchBlockEntry.Instructions[0].MatchStLoc(out var temporaryVariable, out value))
  88. continue;
  89. if (!value.MatchLdLoc(exceptionVariable))
  90. continue;
  91. if (!catchBlockEntry.Instructions[1].MatchStLoc(out objectVariable, out value))
  92. continue;
  93. if (!value.MatchLdLoc(temporaryVariable))
  94. continue;
  95. break;
  96. default:
  97. continue;
  98. }
  99. if (!catchBlockEntry.Instructions[catchBlockEntry.Instructions.Count - 1].MatchBranch(out var entryPointOfFinally))
  100. continue;
  101. // globalCopyVar should only be used once, at the end of the finally-block
  102. if (objectVariable.LoadCount != 1 || objectVariable.StoreCount > 2)
  103. continue;
  104. var beforeExceptionCaptureBlock = (Block)LocalFunctionDecompiler.GetStatement(objectVariable.LoadInstructions[0])?.Parent;
  105. if (beforeExceptionCaptureBlock == null)
  106. continue;
  107. var (afterFinallyBlock, capturePatternStart, objectVariableCopy) = FindBlockAfterFinally(context, beforeExceptionCaptureBlock, objectVariable);
  108. if (afterFinallyBlock == null || capturePatternStart == null)
  109. continue;
  110. var initOfIdentifierVariable = tryCatch.Parent.Children.ElementAtOrDefault(tryCatch.ChildIndex - 1) as StLoc;
  111. if (initOfIdentifierVariable == null || !initOfIdentifierVariable.Value.MatchLdcI4(0))
  112. continue;
  113. var identifierVariable = initOfIdentifierVariable.Variable;
  114. context.StepStartGroup("Inline finally block with await", tryCatch.Handlers[0]);
  115. var cfg = new ControlFlowGraph(container, context.CancellationToken);
  116. changedContainers.Add(container);
  117. context.StepStartGroup("Move blocks to state assignments");
  118. Dictionary<int, Block> identifierValueTargets = new Dictionary<int, Block>();
  119. foreach (var load in identifierVariable.LoadInstructions.ToArray())
  120. {
  121. var statement = LocalFunctionDecompiler.GetStatement(load);
  122. var block = (Block)statement.Parent;
  123. if (!statement.MatchIfInstruction(out var cond, out var branchToTarget))
  124. {
  125. block.Instructions.RemoveAt(statement.ChildIndex);
  126. continue;
  127. }
  128. if (block.Instructions.LastOrDefault() is not Branch otherBranch)
  129. {
  130. block.Instructions.RemoveAt(statement.ChildIndex);
  131. continue;
  132. }
  133. if (cond.MatchCompEquals(out var left, out var right)
  134. && left == load && right.MatchLdcI4(out int value)
  135. && branchToTarget.MatchBranch(out var targetBlock))
  136. {
  137. identifierValueTargets.Add(value, targetBlock);
  138. block.Instructions.RemoveAt(statement.ChildIndex);
  139. }
  140. else if (cond.MatchCompNotEquals(out left, out right)
  141. && left == load && right.MatchLdcI4(out value))
  142. {
  143. identifierValueTargets.Add(value, otherBranch.TargetBlock);
  144. block.Instructions.RemoveAt(statement.ChildIndex + 1);
  145. statement.ReplaceWith(otherBranch);
  146. }
  147. else
  148. {
  149. block.Instructions.RemoveAt(statement.ChildIndex);
  150. }
  151. }
  152. var removedBlocks = new List<Block>();
  153. foreach (var store in identifierVariable.StoreInstructions.OfType<StLoc>().ToArray())
  154. {
  155. if (!store.Value.MatchLdcI4(out int value))
  156. continue;
  157. var statement = LocalFunctionDecompiler.GetStatement(store);
  158. var parent = (Block)statement.Parent;
  159. if (value <= 0)
  160. {
  161. parent.Instructions.RemoveAt(statement.ChildIndex);
  162. continue;
  163. }
  164. if (!identifierValueTargets.TryGetValue(value, out var targetBlock))
  165. {
  166. store.ReplaceWith(new Nop() { Comment = $"Could not find matching block for id {value}" });
  167. continue;
  168. }
  169. var targetContainer = BlockContainer.FindClosestContainer(statement);
  170. context.Step($"Move block with id={value} {targetBlock.Label} to IL_{store.StartILOffset}", statement);
  171. parent.Instructions.RemoveAt(statement.ChildIndex + 1);
  172. store.ReplaceWith(new Branch(targetBlock));
  173. MoveDominatedBlocksToContainer(targetBlock, null, cfg, targetContainer, removedBlocks);
  174. }
  175. context.StepEndGroup(keepIfEmpty: true);
  176. var finallyContainer = new BlockContainer().WithILRange(catchBlockContainer);
  177. tryCatch.ReplaceWith(new TryFinally(tryCatch.TryBlock, finallyContainer).WithILRange(tryCatch.TryBlock));
  178. context.Step("Move blocks into finally", finallyContainer);
  179. MoveDominatedBlocksToContainer(entryPointOfFinally, beforeExceptionCaptureBlock, cfg, finallyContainer, removedBlocks);
  180. if (beforeExceptionCaptureBlock.Instructions.Count >= 3)
  181. {
  182. if (beforeExceptionCaptureBlock.Instructions.SecondToLastOrDefault().MatchIfInstruction(out var cond, out var brInst)
  183. && beforeExceptionCaptureBlock.Instructions.LastOrDefault() is Branch branch
  184. && beforeExceptionCaptureBlock.Instructions[beforeExceptionCaptureBlock.Instructions.Count - 3].MatchStLoc(objectVariableCopy, out var value)
  185. && value.MatchLdLoc(objectVariable))
  186. {
  187. if (cond.MatchCompEqualsNull(out var arg) && arg.MatchLdLoc(objectVariableCopy))
  188. {
  189. context.Step("Simplify end of finally", beforeExceptionCaptureBlock);
  190. beforeExceptionCaptureBlock.Instructions.RemoveRange(beforeExceptionCaptureBlock.Instructions.Count - 3, 2);
  191. branch.ReplaceWith(new Leave(finallyContainer).WithILRange(branch));
  192. }
  193. else if (cond.MatchCompNotEqualsNull(out arg) && arg.MatchLdLoc(objectVariableCopy))
  194. {
  195. context.Step("Simplify end of finally", beforeExceptionCaptureBlock);
  196. beforeExceptionCaptureBlock.Instructions.RemoveRange(beforeExceptionCaptureBlock.Instructions.Count - 3, 2);
  197. branch.ReplaceWith(new Leave(finallyContainer).WithILRange(branch));
  198. }
  199. }
  200. }
  201. foreach (var branch in container.Descendants.OfType<Branch>())
  202. {
  203. if (branch.TargetBlock == entryPointOfFinally && branch.IsDescendantOf(tryCatch.TryBlock))
  204. {
  205. context.Step("branch to finally => branch after finally", branch);
  206. branch.ReplaceWith(new Branch(afterFinallyBlock).WithILRange(branch));
  207. }
  208. else if (branch.TargetBlock == capturePatternStart)
  209. {
  210. if (branch.IsDescendantOf(finallyContainer))
  211. {
  212. context.Step("branch out of finally container => leave finally container", branch);
  213. branch.ReplaceWith(new Leave(finallyContainer).WithILRange(branch));
  214. }
  215. }
  216. }
  217. context.StepEndGroup(keepIfEmpty: true);
  218. }
  219. context.Step("Clean up", function);
  220. // clean up all modified containers
  221. foreach (var container in changedContainers)
  222. container.SortBlocks(deleteUnreachableBlocks: true);
  223. ((BlockContainer)function.Body).SortBlocks(deleteUnreachableBlocks: true);
  224. void MoveDominatedBlocksToContainer(Block newEntryPoint, Block endBlock, ControlFlowGraph graph,
  225. BlockContainer targetContainer, List<Block> removedBlocks)
  226. {
  227. var node = graph.GetNode(newEntryPoint);
  228. var endNode = endBlock == null ? null : graph.GetNode(endBlock);
  229. MoveBlock(newEntryPoint, targetContainer);
  230. foreach (var n in graph.cfg)
  231. {
  232. Block block = (Block)n.UserData;
  233. if (node.Dominates(n))
  234. {
  235. if (endNode != null && endNode != n && endNode.Dominates(n))
  236. continue;
  237. if (block.Parent == targetContainer)
  238. continue;
  239. if (!removedBlocks.Contains(block))
  240. {
  241. MoveBlock(block, targetContainer);
  242. }
  243. }
  244. }
  245. }
  246. void MoveBlock(Block block, BlockContainer target)
  247. {
  248. context.Step($"Move {block.Label} to container at IL_{target.StartILOffset:x4}", target);
  249. block.Remove();
  250. target.Blocks.Add(block);
  251. }
  252. }
  253. static (Block, Block, ILVariable) FindBlockAfterFinally(ILTransformContext context, Block block, ILVariable objectVariable)
  254. {
  255. int count = block.Instructions.Count;
  256. if (count < 3)
  257. return default;
  258. if (!block.Instructions[count - 3].MatchStLoc(out var objectVariableCopy, out var value))
  259. return default;
  260. if (!value.MatchLdLoc(objectVariable))
  261. return default;
  262. if (!block.Instructions[count - 2].MatchIfInstruction(out var cond, out var afterExceptionCaptureBlockBranch))
  263. return default;
  264. if (!afterExceptionCaptureBlockBranch.MatchBranch(out var afterExceptionCaptureBlock))
  265. return default;
  266. if (!block.Instructions[count - 1].MatchBranch(out var exceptionCaptureBlock))
  267. return default;
  268. if (cond.MatchCompEqualsNull(out var arg))
  269. {
  270. if (!arg.MatchLdLoc(objectVariableCopy))
  271. return default;
  272. }
  273. else if (cond.MatchCompNotEqualsNull(out arg))
  274. {
  275. if (!arg.MatchLdLoc(objectVariableCopy))
  276. return default;
  277. (afterExceptionCaptureBlock, exceptionCaptureBlock) = (exceptionCaptureBlock, afterExceptionCaptureBlock);
  278. }
  279. else
  280. {
  281. return default;
  282. }
  283. if (!AwaitInCatchTransform.MatchExceptionCaptureBlock(context, exceptionCaptureBlock,
  284. ref objectVariableCopy, out var store, out _, out _))
  285. {
  286. return default;
  287. }
  288. var exceptionCaptureBlockStart = LocalFunctionDecompiler.GetStatement(store);
  289. if (exceptionCaptureBlockStart == null)
  290. return default;
  291. return (afterExceptionCaptureBlock, (Block)exceptionCaptureBlockStart.Parent, objectVariableCopy);
  292. }
  293. }
  294. }