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.

356 lines
14 KiB

  1. // Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
  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.Linq;
  20. using ICSharpCode.Decompiler.ILAst;
  21. using ICSharpCode.NRefactory.CSharp;
  22. namespace ICSharpCode.Decompiler.Ast.Transforms
  23. {
  24. /// <summary>
  25. /// Add checked/unchecked blocks.
  26. /// </summary>
  27. public class AddCheckedBlocks : IAstTransform
  28. {
  29. #region Annotation
  30. sealed class CheckedUncheckedAnnotation {
  31. /// <summary>
  32. /// true=checked, false=unchecked
  33. /// </summary>
  34. public bool IsChecked;
  35. }
  36. public static readonly object CheckedAnnotation = new CheckedUncheckedAnnotation { IsChecked = true };
  37. public static readonly object UncheckedAnnotation = new CheckedUncheckedAnnotation { IsChecked = false };
  38. #endregion
  39. /*
  40. We treat placing checked/unchecked blocks as an optimization problem, with the following goals:
  41. 1. Use minimum number of checked blocks+expressions
  42. 2. Prefer checked expressions over checked blocks
  43. 3. Make the scope of checked expressions as small as possible
  44. 4. Make the scope of checked blocks as large as possible
  45. (where goal 1 has the highest priority)
  46. */
  47. #region struct Cost
  48. struct Cost
  49. {
  50. // highest possible cost so that the Blocks+Expressions addition doesn't overflow
  51. public static readonly Cost Infinite = new Cost(0x3fffffff, 0x3fffffff);
  52. public readonly int Blocks;
  53. public readonly int Expressions;
  54. public Cost(int blocks, int expressions)
  55. {
  56. this.Blocks = blocks;
  57. this.Expressions = expressions;
  58. }
  59. public static bool operator <(Cost a, Cost b)
  60. {
  61. return a.Blocks + a.Expressions < b.Blocks + b.Expressions
  62. || a.Blocks + a.Expressions == b.Blocks + b.Expressions && a.Blocks < b.Blocks;
  63. }
  64. public static bool operator >(Cost a, Cost b)
  65. {
  66. return a.Blocks + a.Expressions > b.Blocks + b.Expressions
  67. || a.Blocks + a.Expressions == b.Blocks + b.Expressions && a.Blocks > b.Blocks;
  68. }
  69. public static bool operator <=(Cost a, Cost b)
  70. {
  71. return a.Blocks + a.Expressions < b.Blocks + b.Expressions
  72. || a.Blocks + a.Expressions == b.Blocks + b.Expressions && a.Blocks <= b.Blocks;
  73. }
  74. public static bool operator >=(Cost a, Cost b)
  75. {
  76. return a.Blocks + a.Expressions > b.Blocks + b.Expressions
  77. || a.Blocks + a.Expressions == b.Blocks + b.Expressions && a.Blocks >= b.Blocks;
  78. }
  79. public static Cost operator +(Cost a, Cost b)
  80. {
  81. return new Cost(a.Blocks + b.Blocks, a.Expressions + b.Expressions);
  82. }
  83. public override string ToString()
  84. {
  85. return string.Format("[{0} + {1}]", Blocks, Expressions);
  86. }
  87. }
  88. #endregion
  89. #region class InsertedNode
  90. /// <summary>
  91. /// Holds the blocks and expressions that should be inserted
  92. /// </summary>
  93. abstract class InsertedNode
  94. {
  95. public static InsertedNode operator +(InsertedNode a, InsertedNode b)
  96. {
  97. if (a == null)
  98. return b;
  99. if (b == null)
  100. return a;
  101. return new InsertedNodeList(a, b);
  102. }
  103. public abstract void Insert();
  104. }
  105. class InsertedNodeList : InsertedNode
  106. {
  107. readonly InsertedNode child1, child2;
  108. public InsertedNodeList(AddCheckedBlocks.InsertedNode child1, AddCheckedBlocks.InsertedNode child2)
  109. {
  110. this.child1 = child1;
  111. this.child2 = child2;
  112. }
  113. public override void Insert()
  114. {
  115. child1.Insert();
  116. child2.Insert();
  117. }
  118. }
  119. class InsertedExpression : InsertedNode
  120. {
  121. readonly Expression expression;
  122. readonly bool isChecked;
  123. public InsertedExpression(Expression expression, bool isChecked)
  124. {
  125. this.expression = expression;
  126. this.isChecked = isChecked;
  127. }
  128. public override void Insert()
  129. {
  130. if (isChecked)
  131. expression.ReplaceWith(e => new CheckedExpression { Expression = e });
  132. else
  133. expression.ReplaceWith(e => new UncheckedExpression { Expression = e });
  134. }
  135. }
  136. class ConvertCompoundAssignment : InsertedNode
  137. {
  138. readonly Expression expression;
  139. readonly bool isChecked;
  140. public ConvertCompoundAssignment(Expression expression, bool isChecked)
  141. {
  142. this.expression = expression;
  143. this.isChecked = isChecked;
  144. }
  145. public override void Insert()
  146. {
  147. AssignmentExpression assign = expression.Annotation<ReplaceMethodCallsWithOperators.RestoreOriginalAssignOperatorAnnotation>().Restore(expression);
  148. expression.ReplaceWith(assign);
  149. if (isChecked)
  150. assign.Right = new CheckedExpression { Expression = assign.Right.Detach() };
  151. else
  152. assign.Right = new UncheckedExpression { Expression = assign.Right.Detach() };
  153. }
  154. }
  155. class InsertedBlock : InsertedNode
  156. {
  157. readonly Statement firstStatement; // inclusive
  158. readonly Statement lastStatement; // exclusive
  159. readonly bool isChecked;
  160. public InsertedBlock(Statement firstStatement, Statement lastStatement, bool isChecked)
  161. {
  162. this.firstStatement = firstStatement;
  163. this.lastStatement = lastStatement;
  164. this.isChecked = isChecked;
  165. }
  166. public override void Insert()
  167. {
  168. BlockStatement newBlock = new BlockStatement();
  169. // Move all statements except for the first
  170. Statement next;
  171. for (Statement stmt = firstStatement.GetNextStatement(); stmt != lastStatement; stmt = next) {
  172. next = stmt.GetNextStatement();
  173. newBlock.Add(stmt.Detach());
  174. }
  175. // Replace the first statement with the new (un)checked block
  176. if (isChecked)
  177. firstStatement.ReplaceWith(new CheckedStatement { Body = newBlock });
  178. else
  179. firstStatement.ReplaceWith(new UncheckedStatement { Body = newBlock });
  180. // now also move the first node into the new block
  181. newBlock.Statements.InsertAfter(null, firstStatement);
  182. }
  183. }
  184. #endregion
  185. #region class Result
  186. /// <summary>
  187. /// Holds the result of an insertion operation.
  188. /// </summary>
  189. class Result
  190. {
  191. public Cost CostInCheckedContext;
  192. public InsertedNode NodesToInsertInCheckedContext;
  193. public Cost CostInUncheckedContext;
  194. public InsertedNode NodesToInsertInUncheckedContext;
  195. }
  196. #endregion
  197. public void Run(AstNode node)
  198. {
  199. BlockStatement block = node as BlockStatement;
  200. if (block == null) {
  201. for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) {
  202. Run(child);
  203. }
  204. } else {
  205. Result r = GetResultFromBlock(block);
  206. if (r.NodesToInsertInUncheckedContext != null)
  207. r.NodesToInsertInUncheckedContext.Insert();
  208. }
  209. }
  210. Result GetResultFromBlock(BlockStatement block)
  211. {
  212. // For a block, we are tracking 4 possibilities:
  213. // a) context is checked, no unchecked block open
  214. Cost costCheckedContext = new Cost(0, 0);
  215. InsertedNode nodesCheckedContext = null;
  216. // b) context is checked, an unchecked block is open
  217. Cost costCheckedContextUncheckedBlockOpen = Cost.Infinite;
  218. InsertedNode nodesCheckedContextUncheckedBlockOpen = null;
  219. Statement uncheckedBlockStart = null;
  220. // c) context is unchecked, no checked block open
  221. Cost costUncheckedContext = new Cost(0, 0);
  222. InsertedNode nodesUncheckedContext = null;
  223. // d) context is unchecked, a checked block is open
  224. Cost costUncheckedContextCheckedBlockOpen = Cost.Infinite;
  225. InsertedNode nodesUncheckedContextCheckedBlockOpen = null;
  226. Statement checkedBlockStart = null;
  227. Statement statement = block.Statements.FirstOrDefault();
  228. while (true) {
  229. // Blocks can be closed 'for free'. We use '<=' so that blocks are closed as late as possible (goal 4)
  230. if (costCheckedContextUncheckedBlockOpen <= costCheckedContext) {
  231. costCheckedContext = costCheckedContextUncheckedBlockOpen;
  232. nodesCheckedContext = nodesCheckedContextUncheckedBlockOpen + new InsertedBlock(uncheckedBlockStart, statement, false);
  233. }
  234. if (costUncheckedContextCheckedBlockOpen <= costUncheckedContext) {
  235. costUncheckedContext = costUncheckedContextCheckedBlockOpen;
  236. nodesUncheckedContext = nodesUncheckedContextCheckedBlockOpen + new InsertedBlock(checkedBlockStart, statement, true);
  237. }
  238. if (statement == null)
  239. break;
  240. // Now try opening blocks. We use '<' so that blocks are opened as early as possible. (goal 4)
  241. if (costCheckedContext + new Cost(1, 0) < costCheckedContextUncheckedBlockOpen) {
  242. costCheckedContextUncheckedBlockOpen = costCheckedContext + new Cost(1, 0);
  243. nodesCheckedContextUncheckedBlockOpen = nodesCheckedContext;
  244. uncheckedBlockStart = statement;
  245. }
  246. if (costUncheckedContext + new Cost(1, 0) < costUncheckedContextCheckedBlockOpen) {
  247. costUncheckedContextCheckedBlockOpen = costUncheckedContext + new Cost(1, 0);
  248. nodesUncheckedContextCheckedBlockOpen = nodesUncheckedContext;
  249. checkedBlockStart = statement;
  250. }
  251. // Now handle the statement
  252. Result stmtResult = GetResult(statement);
  253. costCheckedContext += stmtResult.CostInCheckedContext;
  254. nodesCheckedContext += stmtResult.NodesToInsertInCheckedContext;
  255. costCheckedContextUncheckedBlockOpen += stmtResult.CostInUncheckedContext;
  256. nodesCheckedContextUncheckedBlockOpen += stmtResult.NodesToInsertInUncheckedContext;
  257. costUncheckedContext += stmtResult.CostInUncheckedContext;
  258. nodesUncheckedContext += stmtResult.NodesToInsertInUncheckedContext;
  259. costUncheckedContextCheckedBlockOpen += stmtResult.CostInCheckedContext;
  260. nodesUncheckedContextCheckedBlockOpen += stmtResult.NodesToInsertInCheckedContext;
  261. statement = statement.GetNextStatement();
  262. }
  263. return new Result {
  264. CostInCheckedContext = costCheckedContext, NodesToInsertInCheckedContext = nodesCheckedContext,
  265. CostInUncheckedContext = costUncheckedContext, NodesToInsertInUncheckedContext = nodesUncheckedContext
  266. };
  267. }
  268. Result GetResult(AstNode node)
  269. {
  270. if (node is BlockStatement)
  271. return GetResultFromBlock((BlockStatement)node);
  272. Result result = new Result();
  273. for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) {
  274. Result childResult = GetResult(child);
  275. result.CostInCheckedContext += childResult.CostInCheckedContext;
  276. result.NodesToInsertInCheckedContext += childResult.NodesToInsertInCheckedContext;
  277. result.CostInUncheckedContext += childResult.CostInUncheckedContext;
  278. result.NodesToInsertInUncheckedContext += childResult.NodesToInsertInUncheckedContext;
  279. }
  280. Expression expr = node as Expression;
  281. if (expr != null) {
  282. CheckedUncheckedAnnotation annotation = expr.Annotation<CheckedUncheckedAnnotation>();
  283. if (annotation != null) {
  284. // If the annotation requires this node to be in a specific context, add a huge cost to the other context
  285. // That huge cost gives us the option to ignore a required checked/unchecked expression when there wouldn't be any
  286. // solution otherwise. (e.g. "for (checked(M().x += 1); true; unchecked(M().x += 2)) {}")
  287. if (annotation.IsChecked)
  288. result.CostInUncheckedContext += new Cost(10000, 0);
  289. else
  290. result.CostInCheckedContext += new Cost(10000, 0);
  291. }
  292. // Embed this node in an checked/unchecked expression:
  293. if (expr.Parent is ExpressionStatement) {
  294. // We cannot use checked/unchecked for top-level-expressions.
  295. // However, we could try converting a compound assignment (checked(a+=b);) or unary operator (checked(a++);)
  296. // back to its old form.
  297. if (expr.Annotation<ReplaceMethodCallsWithOperators.RestoreOriginalAssignOperatorAnnotation>() != null) {
  298. // We use '<' so that expressions are introduced on the deepest level possible (goal 3)
  299. if (result.CostInCheckedContext + new Cost(1, 1) < result.CostInUncheckedContext) {
  300. result.CostInUncheckedContext = result.CostInCheckedContext + new Cost(1, 1);
  301. result.NodesToInsertInUncheckedContext = result.NodesToInsertInCheckedContext + new ConvertCompoundAssignment(expr, true);
  302. } else if (result.CostInUncheckedContext + new Cost(1, 1) < result.CostInCheckedContext) {
  303. result.CostInCheckedContext = result.CostInUncheckedContext + new Cost(1, 1);
  304. result.NodesToInsertInCheckedContext = result.NodesToInsertInUncheckedContext + new ConvertCompoundAssignment(expr, false);
  305. }
  306. }
  307. } else if (expr.Role.IsValid(Expression.Null)) {
  308. // We use '<' so that expressions are introduced on the deepest level possible (goal 3)
  309. if (result.CostInCheckedContext + new Cost(0, 1) < result.CostInUncheckedContext) {
  310. result.CostInUncheckedContext = result.CostInCheckedContext + new Cost(0, 1);
  311. result.NodesToInsertInUncheckedContext = result.NodesToInsertInCheckedContext + new InsertedExpression(expr, true);
  312. } else if (result.CostInUncheckedContext + new Cost(0, 1) < result.CostInCheckedContext) {
  313. result.CostInCheckedContext = result.CostInUncheckedContext + new Cost(0, 1);
  314. result.NodesToInsertInCheckedContext = result.NodesToInsertInUncheckedContext + new InsertedExpression(expr, false);
  315. }
  316. }
  317. }
  318. return result;
  319. }
  320. }
  321. }