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.

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