File: DeclareVariables.cs

package info (click to toggle)
monodevelop 3.0.3.2%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 153,256 kB
  • sloc: cs: 1,020,242; xml: 751,053; makefile: 9,596; sh: 1,529; objc: 302; sql: 129; ansic: 96
file content (337 lines) | stat: -rwxr-xr-x 14,693 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using ICSharpCode.Decompiler.ILAst;
using ICSharpCode.NRefactory.CSharp;
using ICSharpCode.NRefactory.CSharp.Analysis;

namespace ICSharpCode.Decompiler.Ast.Transforms
{
	/// <summary>
	/// Moves variable declarations to improved positions.
	/// </summary>
	public class DeclareVariables : IAstTransform
	{
		sealed class VariableToDeclare
		{
			public AstType Type;
			public string Name;
			public ILVariable ILVariable;
			
			public AssignmentExpression ReplacedAssignment;
			public Statement InsertionPoint;
		}
		
		readonly CancellationToken cancellationToken;
		List<VariableToDeclare> variablesToDeclare = new List<VariableToDeclare>();
		
		public DeclareVariables(DecompilerContext context)
		{
			this.cancellationToken = context.CancellationToken;
		}
		
		public void Run(AstNode node)
		{
			Run(node, null);
			// Declare all the variables at the end, after all the logic has run.
			// This is done so that definite assignment analysis can work on a single representation and doesn't have to be updated
			// when we change the AST.
			foreach (var v in variablesToDeclare) {
				if (v.ReplacedAssignment == null) {
					BlockStatement block = (BlockStatement)v.InsertionPoint.Parent;
					var decl = new VariableDeclarationStatement((AstType)v.Type.Clone(), v.Name);
					if (v.ILVariable != null)
						decl.Variables.Single().AddAnnotation(v.ILVariable);
					block.Statements.InsertBefore(
						v.InsertionPoint,
						decl);
				}
			}
			// First do all the insertions, then do all the replacements. This is necessary because a replacement might remove our reference point from the AST.
			foreach (var v in variablesToDeclare) {
				if (v.ReplacedAssignment != null) {
					// We clone the right expression so that it doesn't get removed from the old ExpressionStatement,
					// which might be still in use by the definite assignment graph.
					VariableInitializer initializer = new VariableInitializer(v.Name, v.ReplacedAssignment.Right.Detach()).CopyAnnotationsFrom(v.ReplacedAssignment).WithAnnotation(v.ILVariable);
					VariableDeclarationStatement varDecl = new VariableDeclarationStatement {
						Type = (AstType)v.Type.Clone(),
						Variables = { initializer }
					};
					ExpressionStatement es = v.ReplacedAssignment.Parent as ExpressionStatement;
					if (es != null) {
						// Note: if this crashes with 'Cannot replace the root node', check whether two variables were assigned the same name
						es.ReplaceWith(varDecl.CopyAnnotationsFrom(es));
					} else {
						v.ReplacedAssignment.ReplaceWith(varDecl);
					}
				}
			}
			variablesToDeclare = null;
		}
		
		void Run(AstNode node, DefiniteAssignmentAnalysis daa)
		{
			BlockStatement block = node as BlockStatement;
			if (block != null) {
				var variables = block.Statements.TakeWhile(stmt => stmt is VariableDeclarationStatement)
					.Cast<VariableDeclarationStatement>().ToList();
				if (variables.Count > 0) {
					// remove old variable declarations:
					foreach (VariableDeclarationStatement varDecl in variables) {
						Debug.Assert(varDecl.Variables.Single().Initializer.IsNull);
						varDecl.Remove();
					}
					if (daa == null) {
						// If possible, reuse the DefiniteAssignmentAnalysis that was created for the parent block
						daa = new DefiniteAssignmentAnalysis(block, cancellationToken);
					}
					foreach (VariableDeclarationStatement varDecl in variables) {
						VariableInitializer initializer = varDecl.Variables.Single();
						string variableName = initializer.Name;
						ILVariable v = initializer.Annotation<ILVariable>();
						bool allowPassIntoLoops = initializer.Annotation<DelegateConstruction.CapturedVariableAnnotation>() == null;
						DeclareVariableInBlock(daa, block, varDecl.Type, variableName, v, allowPassIntoLoops);
					}
				}
			}
			for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) {
				Run(child, daa);
			}
		}
		
		void DeclareVariableInBlock(DefiniteAssignmentAnalysis daa, BlockStatement block, AstType type, string variableName, ILVariable v, bool allowPassIntoLoops)
		{
			// declarationPoint: The point where the variable would be declared, if we decide to declare it in this block
			Statement declarationPoint = null;
			// Check whether we can move down the variable into the sub-blocks
			bool canMoveVariableIntoSubBlocks = FindDeclarationPoint(daa, variableName, allowPassIntoLoops, block, out declarationPoint);
			if (declarationPoint == null) {
				// The variable isn't used at all
				return;
			}
			if (canMoveVariableIntoSubBlocks) {
				// Declare the variable within the sub-blocks
				foreach (Statement stmt in block.Statements) {
					ForStatement forStmt = stmt as ForStatement;
					if (forStmt != null && forStmt.Initializers.Count == 1) {
						// handle the special case of moving a variable into the for initializer
						if (TryConvertAssignmentExpressionIntoVariableDeclaration(forStmt.Initializers.Single(), type, variableName))
							continue;
					}
					UsingStatement usingStmt = stmt as UsingStatement;
					if (usingStmt != null && usingStmt.ResourceAcquisition is AssignmentExpression) {
						// handle the special case of moving a variable into a using statement
						if (TryConvertAssignmentExpressionIntoVariableDeclaration((Expression)usingStmt.ResourceAcquisition, type, variableName))
							continue;
					}
					foreach (AstNode child in stmt.Children) {
						BlockStatement subBlock = child as BlockStatement;
						if (subBlock != null) {
							DeclareVariableInBlock(daa, subBlock, type, variableName, v, allowPassIntoLoops);
						} else if (HasNestedBlocks(child)) {
							foreach (BlockStatement nestedSubBlock in child.Children.OfType<BlockStatement>()) {
								DeclareVariableInBlock(daa, nestedSubBlock, type, variableName, v, allowPassIntoLoops);
							}
						}
					}
				}
			} else {
				// Try converting an assignment expression into a VariableDeclarationStatement
				if (!TryConvertAssignmentExpressionIntoVariableDeclaration(declarationPoint, type, variableName)) {
					// Declare the variable in front of declarationPoint
					variablesToDeclare.Add(new VariableToDeclare { Type = type, Name = variableName, ILVariable = v, InsertionPoint = declarationPoint });
				}
			}
		}

		bool TryConvertAssignmentExpressionIntoVariableDeclaration(Statement declarationPoint, AstType type, string variableName)
		{
			// convert the declarationPoint into a VariableDeclarationStatement
			ExpressionStatement es = declarationPoint as ExpressionStatement;
			if (es != null) {
				return TryConvertAssignmentExpressionIntoVariableDeclaration(es.Expression, type, variableName);
			}
			return false;
		}
		
		bool TryConvertAssignmentExpressionIntoVariableDeclaration(Expression expression, AstType type, string variableName)
		{
			AssignmentExpression ae = expression as AssignmentExpression;
			if (ae != null && ae.Operator == AssignmentOperatorType.Assign) {
				IdentifierExpression ident = ae.Left as IdentifierExpression;
				if (ident != null && ident.Identifier == variableName) {
					variablesToDeclare.Add(new VariableToDeclare { Type = type, Name = variableName, ILVariable = ident.Annotation<ILVariable>(), ReplacedAssignment = ae });
					return true;
				}
			}
			return false;
		}
		
		/// <summary>
		/// Finds the declaration point for the variable within the specified block.
		/// </summary>
		/// <param name="daa">
		/// Definite assignment analysis, must be prepared for 'block' or one of its parents.
		/// </param>
		/// <param name="varDecl">The variable to declare</param>
		/// <param name="block">The block in which the variable should be declared</param>
		/// <param name="declarationPoint">
		/// Output parameter: the first statement within 'block' where the variable needs to be declared.
		/// </param>
		/// <returns>
		/// Returns whether it is possible to move the variable declaration into sub-blocks.
		/// </returns>
		public static bool FindDeclarationPoint(DefiniteAssignmentAnalysis daa, VariableDeclarationStatement varDecl, BlockStatement block, out Statement declarationPoint)
		{
			string variableName = varDecl.Variables.Single().Name;
			bool allowPassIntoLoops = varDecl.Variables.Single().Annotation<DelegateConstruction.CapturedVariableAnnotation>() == null;
			return FindDeclarationPoint(daa, variableName, allowPassIntoLoops, block, out declarationPoint);
		}
		
		static bool FindDeclarationPoint(DefiniteAssignmentAnalysis daa, string variableName, bool allowPassIntoLoops, BlockStatement block, out Statement declarationPoint)
		{
			// declarationPoint: The point where the variable would be declared, if we decide to declare it in this block
			declarationPoint = null;
			foreach (Statement stmt in block.Statements) {
				if (UsesVariable(stmt, variableName)) {
					if (declarationPoint == null)
						declarationPoint = stmt;
					if (!CanMoveVariableUseIntoSubBlock(stmt, variableName, allowPassIntoLoops)) {
						// If it's not possible to move the variable use into a nested block,
						// we need to declare the variable in this block
						return false;
					}
					// If we can move the variable into the sub-block, we need to ensure that the remaining code
					// does not use the value that was assigned by the first sub-block
					Statement nextStatement = stmt.GetNextStatement();
					if (nextStatement != null) {
						// Analyze the range from the next statement to the end of the block
						daa.SetAnalyzedRange(nextStatement, block);
						daa.Analyze(variableName);
						if (daa.UnassignedVariableUses.Count > 0) {
							return false;
						}
					}
				}
			}
			return true;
		}
		
		static bool CanMoveVariableUseIntoSubBlock(Statement stmt, string variableName, bool allowPassIntoLoops)
		{
			if (!allowPassIntoLoops && (stmt is ForStatement || stmt is ForeachStatement || stmt is DoWhileStatement || stmt is WhileStatement))
				return false;
			
			ForStatement forStatement = stmt as ForStatement;
			if (forStatement != null && forStatement.Initializers.Count == 1) {
				// for-statement is special case: we can move variable declarations into the initializer
				ExpressionStatement es = forStatement.Initializers.Single() as ExpressionStatement;
				if (es != null) {
					AssignmentExpression ae = es.Expression as AssignmentExpression;
					if (ae != null && ae.Operator == AssignmentOperatorType.Assign) {
						IdentifierExpression ident = ae.Left as IdentifierExpression;
						if (ident != null && ident.Identifier == variableName) {
							return !UsesVariable(ae.Right, variableName);
						}
					}
				}
			}
			
			UsingStatement usingStatement = stmt as UsingStatement;
			if (usingStatement != null) {
				// using-statement is special case: we can move variable declarations into the initializer
				AssignmentExpression ae = usingStatement.ResourceAcquisition as AssignmentExpression;
				if (ae != null && ae.Operator == AssignmentOperatorType.Assign) {
					IdentifierExpression ident = ae.Left as IdentifierExpression;
					if (ident != null && ident.Identifier == variableName) {
						return !UsesVariable(ae.Right, variableName);
					}
				}
			}
			
			// 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)
			for (AstNode child = stmt.FirstChild; child != null; child = child.NextSibling) {
				if (!(child is BlockStatement) && UsesVariable(child, variableName)) {
					if (HasNestedBlocks(child)) {
						// catch clauses/switch sections can contain nested blocks
						for (AstNode grandchild = child.FirstChild; grandchild != null; grandchild = grandchild.NextSibling) {
							if (!(grandchild is BlockStatement) && UsesVariable(grandchild, variableName))
								return false;
						}
					} else {
						return false;
					}
				}
			}
			return true;
		}
		
		static bool HasNestedBlocks(AstNode node)
		{
			return node is CatchClause || node is SwitchSection;
		}
		
		static bool UsesVariable(AstNode node, string variableName)
		{
			IdentifierExpression ie = node as IdentifierExpression;
			if (ie != null && ie.Identifier == variableName)
				return true;
			
			FixedStatement fixedStatement = node as FixedStatement;
			if (fixedStatement != null) {
				foreach (VariableInitializer v in fixedStatement.Variables) {
					if (v.Name == variableName)
						return false; // no need to introduce the variable here
				}
			}
			
			ForeachStatement foreachStatement = node as ForeachStatement;
			if (foreachStatement != null) {
				if (foreachStatement.VariableName == variableName)
					return false; // no need to introduce the variable here
			}
			
			UsingStatement usingStatement = node as UsingStatement;
			if (usingStatement != null) {
				VariableDeclarationStatement varDecl = usingStatement.ResourceAcquisition as VariableDeclarationStatement;
				if (varDecl != null) {
					foreach (VariableInitializer v in varDecl.Variables) {
						if (v.Name == variableName)
							return false; // no need to introduce the variable here
					}
				}
			}
			
			CatchClause catchClause = node as CatchClause;
			if (catchClause != null && catchClause.VariableName == variableName) {
				return false; // no need to introduce the variable here
			}
			
			for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) {
				if (UsesVariable(child, variableName))
					return true;
			}
			return false;
		}
	}
}