/main/contrib/ICSharpCode.Decompiler/Ast/Transforms/DeclareVariables.cs
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}