PageRenderTime 55ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/main/contrib/ICSharpCode.Decompiler/Ast/Transforms/DeclareVariables.cs

https://github.com/jfcantin/monodevelop
C# | 329 lines | 244 code | 23 blank | 62 comment | 116 complexity | 1b5e48188e101d6d1f41ad780ac2c51b MD5 | raw file
  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.Collections.Generic;
  20. using System.Diagnostics;
  21. using System.Linq;
  22. using System.Threading;
  23. using ICSharpCode.NRefactory.CSharp;
  24. using ICSharpCode.NRefactory.CSharp.Analysis;
  25. namespace ICSharpCode.Decompiler.Ast.Transforms
  26. {
  27. /// <summary>
  28. /// Moves variable declarations to improved positions.
  29. /// </summary>
  30. public class DeclareVariables : IAstTransform
  31. {
  32. sealed class VariableToDeclare
  33. {
  34. public AstType Type;
  35. public string Name;
  36. public AssignmentExpression ReplacedAssignment;
  37. public Statement InsertionPoint;
  38. }
  39. readonly CancellationToken cancellationToken;
  40. List<VariableToDeclare> variablesToDeclare = new List<VariableToDeclare>();
  41. public DeclareVariables(DecompilerContext context)
  42. {
  43. this.cancellationToken = context.CancellationToken;
  44. }
  45. public void Run(AstNode node)
  46. {
  47. Run(node, null);
  48. // Declare all the variables at the end, after all the logic has run.
  49. // This is done so that definite assignment analysis can work on a single representation and doesn't have to be updated
  50. // when we change the AST.
  51. foreach (var v in variablesToDeclare) {
  52. if (v.ReplacedAssignment == null) {
  53. BlockStatement block = (BlockStatement)v.InsertionPoint.Parent;
  54. block.Statements.InsertBefore(
  55. v.InsertionPoint,
  56. new VariableDeclarationStatement((AstType)v.Type.Clone(), v.Name));
  57. }
  58. }
  59. // First do all the insertions, then do all the replacements. This is necessary because a replacement might remove our reference point from the AST.
  60. foreach (var v in variablesToDeclare) {
  61. if (v.ReplacedAssignment != null) {
  62. // We clone the right expression so that it doesn't get removed from the old ExpressionStatement,
  63. // which might be still in use by the definite assignment graph.
  64. VariableDeclarationStatement varDecl = new VariableDeclarationStatement {
  65. Type = (AstType)v.Type.Clone(),
  66. Variables = { new VariableInitializer(v.Name, v.ReplacedAssignment.Right.Detach()).CopyAnnotationsFrom(v.ReplacedAssignment) }
  67. };
  68. ExpressionStatement es = v.ReplacedAssignment.Parent as ExpressionStatement;
  69. if (es != null) {
  70. // Note: if this crashes with 'Cannot replace the root node', check whether two variables were assigned the same name
  71. es.ReplaceWith(varDecl.CopyAnnotationsFrom(es));
  72. } else {
  73. v.ReplacedAssignment.ReplaceWith(varDecl);
  74. }
  75. }
  76. }
  77. variablesToDeclare = null;
  78. }
  79. void Run(AstNode node, DefiniteAssignmentAnalysis daa)
  80. {
  81. BlockStatement block = node as BlockStatement;
  82. if (block != null) {
  83. var variables = block.Statements.TakeWhile(stmt => stmt is VariableDeclarationStatement)
  84. .Cast<VariableDeclarationStatement>().ToList();
  85. if (variables.Count > 0) {
  86. // remove old variable declarations:
  87. foreach (VariableDeclarationStatement varDecl in variables) {
  88. Debug.Assert(varDecl.Variables.Single().Initializer.IsNull);
  89. varDecl.Remove();
  90. }
  91. if (daa == null) {
  92. // If possible, reuse the DefiniteAssignmentAnalysis that was created for the parent block
  93. daa = new DefiniteAssignmentAnalysis(block, cancellationToken);
  94. }
  95. foreach (VariableDeclarationStatement varDecl in variables) {
  96. string variableName = varDecl.Variables.Single().Name;
  97. bool allowPassIntoLoops = varDecl.Variables.Single().Annotation<DelegateConstruction.CapturedVariableAnnotation>() == null;
  98. DeclareVariableInBlock(daa, block, varDecl.Type, variableName, allowPassIntoLoops);
  99. }
  100. }
  101. }
  102. for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) {
  103. Run(child, daa);
  104. }
  105. }
  106. void DeclareVariableInBlock(DefiniteAssignmentAnalysis daa, BlockStatement block, AstType type, string variableName, bool allowPassIntoLoops)
  107. {
  108. // declarationPoint: The point where the variable would be declared, if we decide to declare it in this block
  109. Statement declarationPoint = null;
  110. // Check whether we can move down the variable into the sub-blocks
  111. bool canMoveVariableIntoSubBlocks = FindDeclarationPoint(daa, variableName, allowPassIntoLoops, block, out declarationPoint);
  112. if (declarationPoint == null) {
  113. // The variable isn't used at all
  114. return;
  115. }
  116. if (canMoveVariableIntoSubBlocks) {
  117. // Declare the variable within the sub-blocks
  118. foreach (Statement stmt in block.Statements) {
  119. ForStatement forStmt = stmt as ForStatement;
  120. if (forStmt != null && forStmt.Initializers.Count == 1) {
  121. // handle the special case of moving a variable into the for initializer
  122. if (TryConvertAssignmentExpressionIntoVariableDeclaration(forStmt.Initializers.Single(), type, variableName))
  123. continue;
  124. }
  125. UsingStatement usingStmt = stmt as UsingStatement;
  126. if (usingStmt != null && usingStmt.ResourceAcquisition is AssignmentExpression) {
  127. // handle the special case of moving a variable into a using statement
  128. if (TryConvertAssignmentExpressionIntoVariableDeclaration((Expression)usingStmt.ResourceAcquisition, type, variableName))
  129. continue;
  130. }
  131. foreach (AstNode child in stmt.Children) {
  132. BlockStatement subBlock = child as BlockStatement;
  133. if (subBlock != null) {
  134. DeclareVariableInBlock(daa, subBlock, type, variableName, allowPassIntoLoops);
  135. } else if (HasNestedBlocks(child)) {
  136. foreach (BlockStatement nestedSubBlock in child.Children.OfType<BlockStatement>()) {
  137. DeclareVariableInBlock(daa, nestedSubBlock, type, variableName, allowPassIntoLoops);
  138. }
  139. }
  140. }
  141. }
  142. } else {
  143. // Try converting an assignment expression into a VariableDeclarationStatement
  144. if (!TryConvertAssignmentExpressionIntoVariableDeclaration(declarationPoint, type, variableName)) {
  145. // Declare the variable in front of declarationPoint
  146. variablesToDeclare.Add(new VariableToDeclare { Type = type, Name = variableName, InsertionPoint = declarationPoint });
  147. }
  148. }
  149. }
  150. bool TryConvertAssignmentExpressionIntoVariableDeclaration(Statement declarationPoint, AstType type, string variableName)
  151. {
  152. // convert the declarationPoint into a VariableDeclarationStatement
  153. ExpressionStatement es = declarationPoint as ExpressionStatement;
  154. if (es != null) {
  155. return TryConvertAssignmentExpressionIntoVariableDeclaration(es.Expression, type, variableName);
  156. }
  157. return false;
  158. }
  159. bool TryConvertAssignmentExpressionIntoVariableDeclaration(Expression expression, AstType type, string variableName)
  160. {
  161. AssignmentExpression ae = expression as AssignmentExpression;
  162. if (ae != null && ae.Operator == AssignmentOperatorType.Assign) {
  163. IdentifierExpression ident = ae.Left as IdentifierExpression;
  164. if (ident != null && ident.Identifier == variableName) {
  165. variablesToDeclare.Add(new VariableToDeclare { Type = type, Name = variableName, ReplacedAssignment = ae });
  166. return true;
  167. }
  168. }
  169. return false;
  170. }
  171. /// <summary>
  172. /// Finds the declaration point for the variable within the specified block.
  173. /// </summary>
  174. /// <param name="daa">
  175. /// Definite assignment analysis, must be prepared for 'block' or one of its parents.
  176. /// </param>
  177. /// <param name="varDecl">The variable to declare</param>
  178. /// <param name="block">The block in which the variable should be declared</param>
  179. /// <param name="declarationPoint">
  180. /// Output parameter: the first statement within 'block' where the variable needs to be declared.
  181. /// </param>
  182. /// <returns>
  183. /// Returns whether it is possible to move the variable declaration into sub-blocks.
  184. /// </returns>
  185. public static bool FindDeclarationPoint(DefiniteAssignmentAnalysis daa, VariableDeclarationStatement varDecl, BlockStatement block, out Statement declarationPoint)
  186. {
  187. string variableName = varDecl.Variables.Single().Name;
  188. bool allowPassIntoLoops = varDecl.Variables.Single().Annotation<DelegateConstruction.CapturedVariableAnnotation>() == null;
  189. return FindDeclarationPoint(daa, variableName, allowPassIntoLoops, block, out declarationPoint);
  190. }
  191. static bool FindDeclarationPoint(DefiniteAssignmentAnalysis daa, string variableName, bool allowPassIntoLoops, BlockStatement block, out Statement declarationPoint)
  192. {
  193. // declarationPoint: The point where the variable would be declared, if we decide to declare it in this block
  194. declarationPoint = null;
  195. foreach (Statement stmt in block.Statements) {
  196. if (UsesVariable(stmt, variableName)) {
  197. if (declarationPoint == null)
  198. declarationPoint = stmt;
  199. if (!CanMoveVariableUseIntoSubBlock(stmt, variableName, allowPassIntoLoops)) {
  200. // If it's not possible to move the variable use into a nested block,
  201. // we need to declare the variable in this block
  202. return false;
  203. }
  204. // If we can move the variable into the sub-block, we need to ensure that the remaining code
  205. // does not use the value that was assigned by the first sub-block
  206. Statement nextStatement = stmt.GetNextStatement();
  207. if (nextStatement != null) {
  208. // Analyze the range from the next statement to the end of the block
  209. daa.SetAnalyzedRange(nextStatement, block);
  210. daa.Analyze(variableName);
  211. if (daa.UnassignedVariableUses.Count > 0) {
  212. return false;
  213. }
  214. }
  215. }
  216. }
  217. return true;
  218. }
  219. static bool CanMoveVariableUseIntoSubBlock(Statement stmt, string variableName, bool allowPassIntoLoops)
  220. {
  221. if (!allowPassIntoLoops && (stmt is ForStatement || stmt is ForeachStatement || stmt is DoWhileStatement || stmt is WhileStatement))
  222. return false;
  223. ForStatement forStatement = stmt as ForStatement;
  224. if (forStatement != null && forStatement.Initializers.Count == 1) {
  225. // for-statement is special case: we can move variable declarations into the initializer
  226. ExpressionStatement es = forStatement.Initializers.Single() as ExpressionStatement;
  227. if (es != null) {
  228. AssignmentExpression ae = es.Expression as AssignmentExpression;
  229. if (ae != null && ae.Operator == AssignmentOperatorType.Assign) {
  230. IdentifierExpression ident = ae.Left as IdentifierExpression;
  231. if (ident != null && ident.Identifier == variableName) {
  232. return !UsesVariable(ae.Right, variableName);
  233. }
  234. }
  235. }
  236. }
  237. UsingStatement usingStatement = stmt as UsingStatement;
  238. if (usingStatement != null) {
  239. // using-statement is special case: we can move variable declarations into the initializer
  240. AssignmentExpression ae = usingStatement.ResourceAcquisition as AssignmentExpression;
  241. if (ae != null && ae.Operator == AssignmentOperatorType.Assign) {
  242. IdentifierExpression ident = ae.Left as IdentifierExpression;
  243. if (ident != null && ident.Identifier == variableName) {
  244. return !UsesVariable(ae.Right, variableName);
  245. }
  246. }
  247. }
  248. // We can move the variable into a sub-block only if the variable is used in only that sub-block (and not in expressions such as the loop condition)
  249. for (AstNode child = stmt.FirstChild; child != null; child = child.NextSibling) {
  250. if (!(child is BlockStatement) && UsesVariable(child, variableName)) {
  251. if (HasNestedBlocks(child)) {
  252. // catch clauses/switch sections can contain nested blocks
  253. for (AstNode grandchild = child.FirstChild; grandchild != null; grandchild = grandchild.NextSibling) {
  254. if (!(grandchild is BlockStatement) && UsesVariable(grandchild, variableName))
  255. return false;
  256. }
  257. } else {
  258. return false;
  259. }
  260. }
  261. }
  262. return true;
  263. }
  264. static bool HasNestedBlocks(AstNode node)
  265. {
  266. return node is CatchClause || node is SwitchSection;
  267. }
  268. static bool UsesVariable(AstNode node, string variableName)
  269. {
  270. IdentifierExpression ie = node as IdentifierExpression;
  271. if (ie != null && ie.Identifier == variableName)
  272. return true;
  273. FixedStatement fixedStatement = node as FixedStatement;
  274. if (fixedStatement != null) {
  275. foreach (VariableInitializer v in fixedStatement.Variables) {
  276. if (v.Name == variableName)
  277. return false; // no need to introduce the variable here
  278. }
  279. }
  280. ForeachStatement foreachStatement = node as ForeachStatement;
  281. if (foreachStatement != null) {
  282. if (foreachStatement.VariableName == variableName)
  283. return false; // no need to introduce the variable here
  284. }
  285. UsingStatement usingStatement = node as UsingStatement;
  286. if (usingStatement != null) {
  287. VariableDeclarationStatement varDecl = usingStatement.ResourceAcquisition as VariableDeclarationStatement;
  288. if (varDecl != null) {
  289. foreach (VariableInitializer v in varDecl.Variables) {
  290. if (v.Name == variableName)
  291. return false; // no need to introduce the variable here
  292. }
  293. }
  294. }
  295. CatchClause catchClause = node as CatchClause;
  296. if (catchClause != null && catchClause.VariableName == variableName) {
  297. return false; // no need to introduce the variable here
  298. }
  299. for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) {
  300. if (UsesVariable(child, variableName))
  301. return true;
  302. }
  303. return false;
  304. }
  305. }
  306. }