PageRenderTime 33ms CodeModel.GetById 13ms app.highlight 15ms RepoModel.GetById 1ms app.codeStats 1ms

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