// 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 ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; using ICSharpCode.Decompiler.CSharp.TypeSystem; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.CSharp.Analysis; using Mono.Cecil; namespace ICSharpCode.Decompiler.CSharp.Transforms { /// /// Finds the expanded form of using statements using pattern matching and replaces it with a UsingStatement. /// public sealed class PatternStatementTransform : ContextTrackingVisitor, IAstTransform { TransformContext context; public void Run(AstNode rootNode, TransformContext context) { this.context = context; base.Initialize(context); rootNode.AcceptVisitor(this); } #region Visitor Overrides protected override AstNode VisitChildren(AstNode node) { // Go through the children, and keep visiting a node as long as it changes. // Because some transforms delete/replace nodes before and after the node being transformed, we rely // on the transform's return value to know where we need to keep iterating. for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { AstNode oldChild; do { oldChild = child; child = child.AcceptVisitor(this); Debug.Assert(child != null && child.Parent == node); } while (child != oldChild); } return node; } public override AstNode VisitExpressionStatement(ExpressionStatement expressionStatement) { AstNode result; if (context.Settings.UsingStatement) { result = TransformNonGenericForEach(expressionStatement); if (result != null) return result; result = TransformUsings(expressionStatement); if (result != null) return result; } result = TransformFor(expressionStatement); if (result != null) return result; if (context.Settings.LockStatement) { result = TransformLock(expressionStatement); if (result != null) return result; } return base.VisitExpressionStatement(expressionStatement); } public override AstNode VisitUsingStatement(UsingStatement usingStatement) { if (context.Settings.ForEachStatement) { AstNode result = TransformForeach(usingStatement); if (result != null) return result; } return base.VisitUsingStatement(usingStatement); } public override AstNode VisitWhileStatement(WhileStatement whileStatement) { return TransformDoWhile(whileStatement) ?? base.VisitWhileStatement(whileStatement); } public override AstNode VisitIfElseStatement(IfElseStatement ifElseStatement) { if (context.Settings.SwitchStatementOnString) { AstNode result = TransformSwitchOnString(ifElseStatement); if (result != null) return result; } AstNode simplifiedIfElse = SimplifyCascadingIfElseStatements(ifElseStatement); if (simplifiedIfElse != null) return simplifiedIfElse; return base.VisitIfElseStatement(ifElseStatement); } public override AstNode VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration) { if (context.Settings.AutomaticProperties) { AstNode result = TransformAutomaticProperties(propertyDeclaration); if (result != null) return result; } return base.VisitPropertyDeclaration(propertyDeclaration); } public override AstNode VisitCustomEventDeclaration(CustomEventDeclaration eventDeclaration) { // first apply transforms to the accessor bodies base.VisitCustomEventDeclaration(eventDeclaration); if (context.Settings.AutomaticEvents) { AstNode result = TransformAutomaticEvents(eventDeclaration); if (result != null) return result; } return eventDeclaration; } public override AstNode VisitMethodDeclaration(MethodDeclaration methodDeclaration) { return TransformDestructor(methodDeclaration) ?? base.VisitMethodDeclaration(methodDeclaration); } public override AstNode VisitTryCatchStatement(TryCatchStatement tryCatchStatement) { return TransformTryCatchFinally(tryCatchStatement) ?? base.VisitTryCatchStatement(tryCatchStatement); } #endregion /// /// $variable = $initializer; /// static readonly AstNode variableAssignPattern = new ExpressionStatement( new AssignmentExpression( new NamedNode("variable", new IdentifierExpression(Pattern.AnyString)), new AnyNode("initializer") )); #region using static Expression InvokeDispose(Expression identifier) { return new Choice { identifier.Invoke("Dispose"), identifier.Clone().CastTo(new TypePattern(typeof(IDisposable))).Invoke("Dispose") }; } static readonly AstNode usingTryCatchPattern = new Choice { { "c#/vb", new TryCatchStatement { TryBlock = new AnyNode(), FinallyBlock = new BlockStatement { new Choice { { "valueType", new ExpressionStatement(InvokeDispose(new NamedNode("ident", new IdentifierExpression(Pattern.AnyString)))) }, { "referenceType", new IfElseStatement { Condition = new BinaryOperatorExpression( new NamedNode("ident", new IdentifierExpression(Pattern.AnyString)), BinaryOperatorType.InEquality, new NullReferenceExpression() ), TrueStatement = new BlockStatement { new ExpressionStatement(InvokeDispose(new Backreference("ident"))) } } } }.ToStatement() } } }, { "f#", new TryCatchStatement { TryBlock = new AnyNode(), FinallyBlock = new BlockStatement { new ExpressionStatement( new AssignmentExpression(left: new NamedNode("disposable", new IdentifierExpression(Pattern.AnyString)), right: new AsExpression(expression: new NamedNode("ident", new IdentifierExpression(Pattern.AnyString)), type: new TypePattern(typeof(IDisposable)) ) ) ), new IfElseStatement { Condition = new BinaryOperatorExpression( new Backreference("disposable"), BinaryOperatorType.InEquality, new NullReferenceExpression() ), TrueStatement = new BlockStatement { new ExpressionStatement(InvokeDispose(new Backreference("disposable"))) } } } } } }; public UsingStatement TransformUsings(ExpressionStatement node) { Match m1 = variableAssignPattern.Match(node); if (!m1.Success) return null; TryCatchStatement tryCatch = node.NextSibling as TryCatchStatement; Match m2 = usingTryCatchPattern.Match(tryCatch); if (!m2.Success) return null; IL.ILVariable variable = m1.Get("variable").Single().GetILVariable(); string variableName = m1.Get("variable").Single().Identifier; if (variable == null || variableName != m2.Get("ident").Single().Identifier) return null; if (m2.Has("valueType")) { // if there's no if(x!=null), then it must be a value type if (variable.Type.IsReferenceType != false) return null; } // There are two variants of the using statement: // "using (var a = init)" and "using (expr)". // The former declares a read-only variable 'a', and the latter declares an unnamed read-only variable // to store the original value of 'expr'. // This means that in order to introduce a using statement, in both cases we need to detect a read-only // variable that is used only within that block. if (HasAssignment(tryCatch, variableName)) return null; VariableDeclarationStatement varDecl = FindVariableDeclaration(node, variableName); if (varDecl == null || !(varDecl.Parent is BlockStatement)) return null; // Validate that the variable is not used after the using statement: if (!IsVariableValueUnused(varDecl, tryCatch)) return null; if (m2.Has("f#")) { string variableNameDisposable = m2.Get("disposable").Single().Identifier; VariableDeclarationStatement varDeclDisposable = FindVariableDeclaration(node, variableNameDisposable); if (varDeclDisposable == null || !(varDeclDisposable.Parent is BlockStatement)) return null; // Validate that the variable is not used after the using statement: if (!IsVariableValueUnused(varDeclDisposable, tryCatch)) return null; } node.Remove(); UsingStatement usingStatement = new UsingStatement(); usingStatement.EmbeddedStatement = tryCatch.TryBlock.Detach(); tryCatch.ReplaceWith(usingStatement); // If possible, we'll eliminate the variable completely: if (usingStatement.EmbeddedStatement.Descendants.OfType().Any(ident => ident.Identifier == variableName)) { // variable is used, so we'll create a variable declaration usingStatement.ResourceAcquisition = new VariableDeclarationStatement { Type = (AstType)varDecl.Type.Clone(), Variables = { new VariableInitializer { Name = variableName, Initializer = m1.Get("initializer").Single().Detach() }.CopyAnnotationsFrom(node.Expression) .WithAnnotation(variable) } }.CopyAnnotationsFrom(node); } else { // the variable is never used; eliminate it: usingStatement.ResourceAcquisition = m1.Get("initializer").Single().Detach(); } return usingStatement; } internal static VariableDeclarationStatement FindVariableDeclaration(AstNode node, string identifier) { while (node != null) { while (node.PrevSibling != null) { node = node.PrevSibling; VariableDeclarationStatement varDecl = node as VariableDeclarationStatement; if (varDecl != null && varDecl.Variables.Count == 1 && varDecl.Variables.Single().Name == identifier) { return varDecl; } } node = node.Parent; } return null; } /// /// Gets whether the old variable value (assigned inside 'targetStatement' or earlier) /// is read anywhere in the remaining scope of the variable declaration. /// bool IsVariableValueUnused(VariableDeclarationStatement varDecl, Statement targetStatement) { Debug.Assert(targetStatement.Ancestors.Contains(varDecl.Parent)); BlockStatement block = (BlockStatement)varDecl.Parent; DefiniteAssignmentAnalysis daa = CreateDAA(block); daa.SetAnalyzedRange(targetStatement, block, startInclusive: false); daa.Analyze(varDecl.Variables.Single().Name); return daa.UnassignedVariableUses.Count == 0; } // I used this in the first implementation of the using-statement transform, but now no longer // because there were problems when multiple using statements were using the same variable // - no single using statement could be transformed without making the C# code invalid, // but transforming both would work. // We now use 'IsVariableValueUnused' which will perform the transform // even if it results in two variables with the same name and overlapping scopes. // (this issue could be fixed later by renaming one of the variables) // I'm not sure whether the other consumers of 'CanMoveVariableDeclarationIntoStatement' should be changed the same way. bool CanMoveVariableDeclarationIntoStatement(VariableDeclarationStatement varDecl, Statement targetStatement, out Statement declarationPoint) { Debug.Assert(targetStatement.Ancestors.Contains(varDecl.Parent)); // Find all blocks between targetStatement and varDecl.Parent List blocks = targetStatement.Ancestors.TakeWhile(block => block != varDecl.Parent).OfType().ToList(); blocks.Add((BlockStatement)varDecl.Parent); // also handle the varDecl.Parent block itself blocks.Reverse(); // go from parent blocks to child blocks var daa = CreateDAA(blocks[0]); declarationPoint = null; return false; /*foreach (BlockStatement block in blocks) { if (!DeclareVariables.FindDeclarationPoint(daa, varDecl, block, out declarationPoint)) { return false; } } return true;*/ } private DefiniteAssignmentAnalysis CreateDAA(BlockStatement block) { var typeResolveContext = new CSharpTypeResolveContext(context.TypeSystem.MainAssembly); return new DefiniteAssignmentAnalysis(block, (node, ct) => node.GetResolveResult(), typeResolveContext, context.CancellationToken); } /// /// Gets whether there is an assignment to 'variableName' anywhere within the given node. /// bool HasAssignment(AstNode root, string variableName) { foreach (AstNode node in root.DescendantsAndSelf) { IdentifierExpression ident = node as IdentifierExpression; if (ident != null && ident.Identifier == variableName) { if (ident.Parent is AssignmentExpression && ident.Role == AssignmentExpression.LeftRole || ident.Parent is DirectionExpression) { return true; } } } return false; } #endregion #region foreach (generic) static readonly UsingStatement genericForeachPattern = new UsingStatement { ResourceAcquisition = new VariableDeclarationStatement { Type = new AnyNode("enumeratorType"), Variables = { new NamedNode( "enumeratorVariable", new VariableInitializer { Name = Pattern.AnyString, Initializer = new AnyNode("collection").ToExpression().Invoke("GetEnumerator") } ) } }, EmbeddedStatement = new BlockStatement { new Repeat( new VariableDeclarationStatement { Type = new AnyNode(), Variables = { new VariableInitializer(Pattern.AnyString) } }.WithName("variablesOutsideLoop") ).ToStatement(), new WhileStatement { Condition = new IdentifierExpressionBackreference("enumeratorVariable").ToExpression().Invoke("MoveNext"), EmbeddedStatement = new BlockStatement { new Repeat( new VariableDeclarationStatement { Type = new AnyNode(), Variables = { new VariableInitializer(Pattern.AnyString) } }.WithName("variablesInsideLoop") ).ToStatement(), new AssignmentExpression { Left = new IdentifierExpression(Pattern.AnyString).WithName("itemVariable"), Operator = AssignmentOperatorType.Assign, Right = new IdentifierExpressionBackreference("enumeratorVariable").ToExpression().Member("Current") }, new Repeat(new AnyNode("statement")).ToStatement() } }.WithName("loop") }}; public ForeachStatement TransformForeach(UsingStatement node) { Match m = genericForeachPattern.Match(node); if (!m.Success) return null; if (!(node.Parent is BlockStatement) && m.Has("variablesOutsideLoop")) { // if there are variables outside the loop, we need to put those into the parent block, and that won't work if the direct parent isn't a block return null; } VariableInitializer enumeratorVar = m.Get("enumeratorVariable").Single(); IdentifierExpression itemVar = m.Get("itemVariable").Single(); WhileStatement loop = m.Get("loop").Single(); // Find the declaration of the item variable: // Because we look only outside the loop, we won't make the mistake of moving a captured variable across the loop boundary VariableDeclarationStatement itemVarDecl = FindVariableDeclaration(loop, itemVar.Identifier); if (itemVarDecl == null || !(itemVarDecl.Parent is BlockStatement)) return null; // Now verify that we can move the variable declaration in front of the loop: Statement declarationPoint; CanMoveVariableDeclarationIntoStatement(itemVarDecl, loop, out declarationPoint); // We ignore the return value because we don't care whether we can move the variable into the loop // (that is possible only with non-captured variables). // We just care that we can move it in front of the loop: if (declarationPoint != loop) return null; // Make sure that the enumerator variable is not used inside the body var enumeratorId = Identifier.Create(enumeratorVar.Name); foreach (Statement stmt in m.Get("statement")) { if (stmt.Descendants.OfType().Any(id => enumeratorId.IsMatch(id))) return null; } BlockStatement newBody = new BlockStatement(); foreach (Statement stmt in m.Get("variablesInsideLoop")) newBody.Add(stmt.Detach()); foreach (Statement stmt in m.Get("statement")) newBody.Add(stmt.Detach()); ForeachStatement foreachStatement = new ForeachStatement { VariableType = (AstType)itemVarDecl.Type.Clone(), VariableName = itemVar.Identifier, InExpression = m.Get("collection").Single().Detach(), EmbeddedStatement = newBody }.WithAnnotation(itemVarDecl.Variables.Single().Annotation()); if (foreachStatement.InExpression is BaseReferenceExpression) { foreachStatement.InExpression = new ThisReferenceExpression().CopyAnnotationsFrom(foreachStatement.InExpression); } node.ReplaceWith(foreachStatement); foreach (Statement stmt in m.Get("variablesOutsideLoop")) { ((BlockStatement)foreachStatement.Parent).Statements.InsertAfter(null, stmt.Detach()); } return foreachStatement; } #endregion #region foreach (non-generic) ExpressionStatement getEnumeratorPattern = new ExpressionStatement( new AssignmentExpression( new NamedNode("left", new IdentifierExpression(Pattern.AnyString)), new AnyNode("collection").ToExpression().Invoke("GetEnumerator") )); TryCatchStatement nonGenericForeachPattern = new TryCatchStatement { TryBlock = new BlockStatement { new WhileStatement { Condition = new IdentifierExpression(Pattern.AnyString).WithName("enumerator").Invoke("MoveNext"), EmbeddedStatement = new BlockStatement { new AssignmentExpression( new IdentifierExpression(Pattern.AnyString).WithName("itemVar"), new Choice { new Backreference("enumerator").ToExpression().Member("Current"), new CastExpression { Type = new AnyNode("castType"), Expression = new Backreference("enumerator").ToExpression().Member("Current") } } ), new Repeat(new AnyNode("stmt")).ToStatement() } }.WithName("loop") }, FinallyBlock = new BlockStatement { new AssignmentExpression( new IdentifierExpression(Pattern.AnyString).WithName("disposable"), new Backreference("enumerator").ToExpression().CastAs(new TypePattern(typeof(IDisposable))) ), new IfElseStatement { Condition = new BinaryOperatorExpression { Left = new Backreference("disposable"), Operator = BinaryOperatorType.InEquality, Right = new NullReferenceExpression() }, TrueStatement = new BlockStatement { new Backreference("disposable").ToExpression().Invoke("Dispose") } } }}; public ForeachStatement TransformNonGenericForEach(ExpressionStatement node) { Match m1 = getEnumeratorPattern.Match(node); if (!m1.Success) return null; AstNode tryCatch = node.NextSibling; Match m2 = nonGenericForeachPattern.Match(tryCatch); if (!m2.Success) return null; IdentifierExpression enumeratorVar = m2.Get("enumerator").Single(); IdentifierExpression itemVar = m2.Get("itemVar").Single(); WhileStatement loop = m2.Get("loop").Single(); // verify that the getEnumeratorPattern assigns to the same variable as the nonGenericForeachPattern is reading from if (!enumeratorVar.IsMatch(m1.Get("left").Single())) return null; VariableDeclarationStatement enumeratorVarDecl = FindVariableDeclaration(loop, enumeratorVar.Identifier); if (enumeratorVarDecl == null || !(enumeratorVarDecl.Parent is BlockStatement)) return null; // Find the declaration of the item variable: // Because we look only outside the loop, we won't make the mistake of moving a captured variable across the loop boundary VariableDeclarationStatement itemVarDecl = FindVariableDeclaration(loop, itemVar.Identifier); if (itemVarDecl == null || !(itemVarDecl.Parent is BlockStatement)) return null; // Now verify that we can move the variable declaration in front of the loop: Statement declarationPoint; CanMoveVariableDeclarationIntoStatement(itemVarDecl, loop, out declarationPoint); // We ignore the return value because we don't care whether we can move the variable into the loop // (that is possible only with non-captured variables). // We just care that we can move it in front of the loop: if (declarationPoint != loop) return null; ForeachStatement foreachStatement = new ForeachStatement { VariableType = itemVarDecl.Type.Clone(), VariableName = itemVar.Identifier, }.WithAnnotation(itemVarDecl.Variables.Single().Annotation()); BlockStatement body = new BlockStatement(); foreachStatement.EmbeddedStatement = body; ((BlockStatement)node.Parent).Statements.InsertBefore(node, foreachStatement); body.Add(node.Detach()); body.Add((Statement)tryCatch.Detach()); // Now that we moved the whole try-catch into the foreach loop; verify that we can // move the enumerator into the foreach loop: CanMoveVariableDeclarationIntoStatement(enumeratorVarDecl, foreachStatement, out declarationPoint); if (declarationPoint != foreachStatement) { // oops, the enumerator variable can't be moved into the foreach loop // Undo our AST changes: ((BlockStatement)foreachStatement.Parent).Statements.InsertBefore(foreachStatement, node.Detach()); foreachStatement.ReplaceWith(tryCatch); return null; } // Now create the correct body for the foreach statement: foreachStatement.InExpression = m1.Get("collection").Single().Detach(); if (foreachStatement.InExpression is BaseReferenceExpression) { foreachStatement.InExpression = new ThisReferenceExpression().CopyAnnotationsFrom(foreachStatement.InExpression); } body.Statements.Clear(); body.Statements.AddRange(m2.Get("stmt").Select(stmt => stmt.Detach())); return foreachStatement; } #endregion #region for static readonly WhileStatement forPattern = new WhileStatement { Condition = new BinaryOperatorExpression { Left = new NamedNode("ident", new IdentifierExpression(Pattern.AnyString)), Operator = BinaryOperatorType.Any, Right = new AnyNode("endExpr") }, EmbeddedStatement = new BlockStatement { Statements = { new Repeat(new AnyNode("statement")), new NamedNode( "increment", new ExpressionStatement( new AssignmentExpression { Left = new Backreference("ident"), Operator = AssignmentOperatorType.Any, Right = new AnyNode() })) } }}; public ForStatement TransformFor(ExpressionStatement node) { Match m1 = variableAssignPattern.Match(node); if (!m1.Success) return null; AstNode next = node.NextSibling; Match m2 = forPattern.Match(next); if (!m2.Success) return null; // ensure the variable in the for pattern is the same as in the declaration if (m1.Get("variable").Single().Identifier != m2.Get("ident").Single().Identifier) return null; WhileStatement loop = (WhileStatement)next; node.Remove(); BlockStatement newBody = new BlockStatement(); foreach (Statement stmt in m2.Get("statement")) newBody.Add(stmt.Detach()); ForStatement forStatement = new ForStatement(); forStatement.Initializers.Add(node); forStatement.Condition = loop.Condition.Detach(); forStatement.Iterators.Add(m2.Get("increment").Single().Detach()); forStatement.EmbeddedStatement = newBody; loop.ReplaceWith(forStatement); return forStatement; } #endregion #region doWhile static readonly WhileStatement doWhilePattern = new WhileStatement { Condition = new PrimitiveExpression(true), EmbeddedStatement = new BlockStatement { Statements = { new Repeat(new AnyNode("statement")), new IfElseStatement { Condition = new AnyNode("condition"), TrueStatement = new BlockStatement { new BreakStatement() } } } }}; public DoWhileStatement TransformDoWhile(WhileStatement whileLoop) { Match m = doWhilePattern.Match(whileLoop); if (m.Success) { DoWhileStatement doLoop = new DoWhileStatement(); doLoop.Condition = new UnaryOperatorExpression(UnaryOperatorType.Not, m.Get("condition").Single().Detach()); //doLoop.Condition.AcceptVisitor(new PushNegation(), null); BlockStatement block = (BlockStatement)whileLoop.EmbeddedStatement; block.Statements.Last().Remove(); // remove if statement doLoop.EmbeddedStatement = block.Detach(); whileLoop.ReplaceWith(doLoop); // we may have to extract variable definitions out of the loop if they were used in the condition: foreach (var varDecl in block.Statements.OfType()) { VariableInitializer v = varDecl.Variables.Single(); if (doLoop.Condition.DescendantsAndSelf.OfType().Any(i => i.Identifier == v.Name)) { AssignmentExpression assign = new AssignmentExpression(new IdentifierExpression(v.Name), v.Initializer.Detach()); // move annotations from v to assign: assign.CopyAnnotationsFrom(v); v.RemoveAnnotations(); // remove varDecl with assignment; and move annotations from varDecl to the ExpressionStatement: varDecl.ReplaceWith(new ExpressionStatement(assign).CopyAnnotationsFrom(varDecl)); varDecl.RemoveAnnotations(); // insert the varDecl above the do-while loop: doLoop.Parent.InsertChildBefore(doLoop, varDecl, BlockStatement.StatementRole); } } return doLoop; } return null; } #endregion #region lock static readonly AstNode lockFlagInitPattern = new ExpressionStatement( new AssignmentExpression( new NamedNode("variable", new IdentifierExpression(Pattern.AnyString)), new PrimitiveExpression(false) )); static readonly AstNode lockTryCatchPattern = new TryCatchStatement { TryBlock = new BlockStatement { new OptionalNode(new VariableDeclarationStatement()).ToStatement(), new TypePattern(typeof(System.Threading.Monitor)).ToType().Invoke( "Enter", new AnyNode("enter"), new DirectionExpression { FieldDirection = FieldDirection.Ref, Expression = new NamedNode("flag", new IdentifierExpression(Pattern.AnyString)) }), new Repeat(new AnyNode()).ToStatement() }, FinallyBlock = new BlockStatement { new IfElseStatement { Condition = new Backreference("flag"), TrueStatement = new BlockStatement { new TypePattern(typeof(System.Threading.Monitor)).ToType().Invoke("Exit", new AnyNode("exit")) } } }}; static readonly AstNode oldMonitorCallPattern = new ExpressionStatement( new TypePattern(typeof(System.Threading.Monitor)).ToType().Invoke("Enter", new AnyNode("enter")) ); static readonly AstNode oldLockTryCatchPattern = new TryCatchStatement { TryBlock = new BlockStatement { new Repeat(new AnyNode()).ToStatement() }, FinallyBlock = new BlockStatement { new TypePattern(typeof(System.Threading.Monitor)).ToType().Invoke("Exit", new AnyNode("exit")) } }; bool AnalyzeLockV2(ExpressionStatement node, out Expression enter, out Expression exit) { enter = null; exit = null; Match m1 = oldMonitorCallPattern.Match(node); if (!m1.Success) return false; Match m2 = oldLockTryCatchPattern.Match(node.NextSibling); if (!m2.Success) return false; enter = m1.Get("enter").Single(); exit = m2.Get("exit").Single(); return true; } bool AnalyzeLockV4(ExpressionStatement node, out Expression enter, out Expression exit) { enter = null; exit = null; Match m1 = lockFlagInitPattern.Match(node); if (!m1.Success) return false; Match m2 = lockTryCatchPattern.Match(node.NextSibling); if (!m2.Success) return false; enter = m2.Get("enter").Single(); exit = m2.Get("exit").Single(); return m1.Get("variable").Single().Identifier == m2.Get("flag").Single().Identifier; } public LockStatement TransformLock(ExpressionStatement node) { Expression enter, exit; bool isV2 = AnalyzeLockV2(node, out enter, out exit); if (isV2 || AnalyzeLockV4(node, out enter, out exit)) { AstNode tryCatch = node.NextSibling; if (!exit.IsMatch(enter)) { // If exit and enter are not the same, then enter must be "exit = ..." AssignmentExpression assign = enter as AssignmentExpression; if (assign == null) return null; if (!exit.IsMatch(assign.Left)) return null; enter = assign.Right; // TODO: verify that 'obj' variable can be removed } // TODO: verify that 'flag' variable can be removed // transform the code into a lock statement: LockStatement l = new LockStatement(); l.Expression = enter.Detach(); l.EmbeddedStatement = ((TryCatchStatement)tryCatch).TryBlock.Detach(); if (!isV2) // Remove 'Enter()' call ((BlockStatement)l.EmbeddedStatement).Statements.First().Remove(); tryCatch.ReplaceWith(l); node.Remove(); // remove flag variable return l; } return null; } #endregion #region switch on strings static readonly IfElseStatement switchOnStringPattern = new IfElseStatement { Condition = new BinaryOperatorExpression { Left = new AnyNode("switchExpr"), Operator = BinaryOperatorType.InEquality, Right = new NullReferenceExpression() }, TrueStatement = new BlockStatement { new IfElseStatement { Condition = new BinaryOperatorExpression { Left = new AnyNode("cachedDict"), Operator = BinaryOperatorType.Equality, Right = new NullReferenceExpression() }, TrueStatement = new AnyNode("dictCreation") }, new IfElseStatement { Condition = new Backreference("cachedDict").ToExpression().Invoke( "TryGetValue", new NamedNode("switchVar", new IdentifierExpression(Pattern.AnyString)), new DirectionExpression { FieldDirection = FieldDirection.Out, Expression = new IdentifierExpression(Pattern.AnyString).WithName("intVar") }), TrueStatement = new BlockStatement { Statements = { new NamedNode( "switch", new SwitchStatement { Expression = new IdentifierExpressionBackreference("intVar"), SwitchSections = { new Repeat(new AnyNode()) } }) } } }, new Repeat(new AnyNode("nonNullDefaultStmt")).ToStatement() }, FalseStatement = new OptionalNode("nullStmt", new BlockStatement { Statements = { new Repeat(new AnyNode()) } }) }; public SwitchStatement TransformSwitchOnString(IfElseStatement node) { Match m = switchOnStringPattern.Match(node); if (!m.Success) return null; // switchVar must be the same as switchExpr; or switchExpr must be an assignment and switchVar the left side of that assignment if (!m.Get("switchVar").Single().IsMatch(m.Get("switchExpr").Single())) { AssignmentExpression assign = m.Get("switchExpr").Single() as AssignmentExpression; if (!(assign != null && m.Get("switchVar").Single().IsMatch(assign.Left))) return null; } FieldReference cachedDictField = m.Get("cachedDict").Single().Annotation(); if (cachedDictField == null) return null; List dictCreation = m.Get("dictCreation").Single().Statements.ToList(); List> dict = BuildDictionary(dictCreation); SwitchStatement sw = m.Get("switch").Single(); sw.Expression = m.Get("switchExpr").Single().Detach(); foreach (SwitchSection section in sw.SwitchSections) { List labels = section.CaseLabels.ToList(); section.CaseLabels.Clear(); foreach (CaseLabel label in labels) { PrimitiveExpression expr = label.Expression as PrimitiveExpression; if (expr == null || !(expr.Value is int)) continue; int val = (int)expr.Value; foreach (var pair in dict) { if (pair.Value == val) section.CaseLabels.Add(new CaseLabel { Expression = new PrimitiveExpression(pair.Key) }); } } } if (m.Has("nullStmt")) { SwitchSection section = new SwitchSection(); section.CaseLabels.Add(new CaseLabel { Expression = new NullReferenceExpression() }); BlockStatement block = m.Get("nullStmt").Single(); block.Statements.Add(new BreakStatement()); section.Statements.Add(block.Detach()); sw.SwitchSections.Add(section); } else if (m.Has("nonNullDefaultStmt")) { sw.SwitchSections.Add( new SwitchSection { CaseLabels = { new CaseLabel { Expression = new NullReferenceExpression() } }, Statements = { new BlockStatement { new BreakStatement() } } }); } if (m.Has("nonNullDefaultStmt")) { SwitchSection section = new SwitchSection(); section.CaseLabels.Add(new CaseLabel()); BlockStatement block = new BlockStatement(); block.Statements.AddRange(m.Get("nonNullDefaultStmt").Select(s => s.Detach())); block.Add(new BreakStatement()); section.Statements.Add(block); sw.SwitchSections.Add(section); } node.ReplaceWith(sw); return sw; } List> BuildDictionary(List dictCreation) { if (context.Settings.ObjectOrCollectionInitializers && dictCreation.Count == 1) return BuildDictionaryFromInitializer(dictCreation[0]); return BuildDictionaryFromAddMethodCalls(dictCreation); } static readonly Statement assignInitializedDictionary = new ExpressionStatement { Expression = new AssignmentExpression { Left = new AnyNode().ToExpression(), Right = new ObjectCreateExpression { Type = new AnyNode(), Arguments = { new Repeat(new AnyNode()) }, Initializer = new ArrayInitializerExpression { Elements = { new Repeat(new AnyNode("dictJumpTable")) } } }, }, }; private List> BuildDictionaryFromInitializer(Statement statement) { List> dict = new List>(); Match m = assignInitializedDictionary.Match(statement); if (!m.Success) return dict; foreach (ArrayInitializerExpression initializer in m.Get("dictJumpTable")) { KeyValuePair pair; if (TryGetPairFrom(initializer.Elements, out pair)) dict.Add(pair); } return dict; } private static List> BuildDictionaryFromAddMethodCalls(List dictCreation) { List> dict = new List>(); for (int i = 0; i < dictCreation.Count; i++) { ExpressionStatement es = dictCreation[i] as ExpressionStatement; if (es == null) continue; InvocationExpression ie = es.Expression as InvocationExpression; if (ie == null) continue; KeyValuePair pair; if (TryGetPairFrom(ie.Arguments, out pair)) dict.Add(pair); } return dict; } private static bool TryGetPairFrom(AstNodeCollection expressions, out KeyValuePair pair) { PrimitiveExpression arg1 = expressions.ElementAtOrDefault(0) as PrimitiveExpression; PrimitiveExpression arg2 = expressions.ElementAtOrDefault(1) as PrimitiveExpression; if (arg1 != null && arg2 != null && arg1.Value is string && arg2.Value is int) { pair = new KeyValuePair((string)arg1.Value, (int)arg2.Value); return true; } pair = default(KeyValuePair); return false; } #endregion #region Automatic Properties static readonly PropertyDeclaration automaticPropertyPattern = new PropertyDeclaration { Attributes = { new Repeat(new AnyNode()) }, Modifiers = Modifiers.Any, ReturnType = new AnyNode(), PrivateImplementationType = new OptionalNode(new AnyNode()), Name = Pattern.AnyString, Getter = new Accessor { Attributes = { new Repeat(new AnyNode()) }, Modifiers = Modifiers.Any, Body = new BlockStatement { new ReturnStatement { Expression = new AnyNode("fieldReference") } } }, Setter = new Accessor { Attributes = { new Repeat(new AnyNode()) }, Modifiers = Modifiers.Any, Body = new BlockStatement { new AssignmentExpression { Left = new Backreference("fieldReference"), Right = new IdentifierExpression("value") } }}}; PropertyDeclaration TransformAutomaticProperties(PropertyDeclaration property) { PropertyDefinition cecilProperty = context.TypeSystem.GetCecil(property.GetSymbol() as IProperty) as PropertyDefinition; if (cecilProperty == null || cecilProperty.GetMethod == null || cecilProperty.SetMethod == null) return null; if (!(cecilProperty.GetMethod.IsCompilerGenerated() && cecilProperty.SetMethod.IsCompilerGenerated())) return null; Match m = automaticPropertyPattern.Match(property); if (m.Success) { var fieldInfo = m.Get("fieldReference").Single().GetSymbol() as IField; if (fieldInfo == null) return null; FieldDefinition field = context.TypeSystem.GetCecil(fieldInfo) as FieldDefinition; if (field.IsCompilerGenerated() && field.DeclaringType == cecilProperty.DeclaringType) { RemoveCompilerGeneratedAttribute(property.Getter.Attributes); RemoveCompilerGeneratedAttribute(property.Setter.Attributes); property.Getter.Body = null; property.Setter.Body = null; } } // Since the property instance is not changed, we can continue in the visitor as usual, so return null return null; } void RemoveCompilerGeneratedAttribute(AstNodeCollection attributeSections) { foreach (AttributeSection section in attributeSections) { foreach (var attr in section.Attributes) { var tr = attr.Type.GetSymbol() as IType; if (tr != null && tr.Namespace == "System.Runtime.CompilerServices" && tr.Name == "CompilerGeneratedAttribute") { attr.Remove(); } } if (section.Attributes.Count == 0) section.Remove(); } } #endregion #region Automatic Events static readonly Accessor automaticEventPatternV4 = new Accessor { Attributes = { new Repeat(new AnyNode()) }, Body = new BlockStatement { new VariableDeclarationStatement { Type = new AnyNode("type"), Variables = { new AnyNode() } }, new VariableDeclarationStatement { Type = new Backreference("type"), Variables = { new AnyNode() } }, new VariableDeclarationStatement { Type = new Backreference("type"), Variables = { new AnyNode() } }, new AssignmentExpression { Left = new NamedNode("var1", new IdentifierExpression(Pattern.AnyString)), Operator = AssignmentOperatorType.Assign, Right = new NamedNode( "field", new MemberReferenceExpression { Target = new Choice { new ThisReferenceExpression(), new TypeReferenceExpression { Type = new AnyNode() } }, MemberName = Pattern.AnyString }) }, new DoWhileStatement { EmbeddedStatement = new BlockStatement { new AssignmentExpression(new NamedNode("var2", new IdentifierExpression(Pattern.AnyString)), new IdentifierExpressionBackreference("var1")), new AssignmentExpression { Left = new NamedNode("var3", new IdentifierExpression(Pattern.AnyString)), Operator = AssignmentOperatorType.Assign, Right = new AnyNode("delegateCombine").ToExpression().Invoke( new IdentifierExpressionBackreference("var2"), new IdentifierExpression("value") ).CastTo(new Backreference("type")) }, new AssignmentExpression { Left = new IdentifierExpressionBackreference("var1"), Right = new TypePattern(typeof(System.Threading.Interlocked)).ToType().Invoke( "CompareExchange", new AstType[] { new Backreference("type") }, // type argument new Expression[] { // arguments new DirectionExpression { FieldDirection = FieldDirection.Ref, Expression = new Backreference("field") }, new IdentifierExpressionBackreference("var3"), new IdentifierExpressionBackreference("var2") } )} }, Condition = new BinaryOperatorExpression { Left = new IdentifierExpressionBackreference("var1"), Operator = BinaryOperatorType.InEquality, Right = new IdentifierExpressionBackreference("var2") }} }}; bool CheckAutomaticEventV4Match(Match m, CustomEventDeclaration ev, bool isAddAccessor) { if (!m.Success) return false; if (m.Get("field").Single().MemberName != ev.Name) return false; // field name must match event name if (!ev.ReturnType.IsMatch(m.Get("type").Single())) return false; // variable types must match event type var combineMethod = m.Get("delegateCombine").Single().Parent.Annotation(); if (combineMethod == null || combineMethod.Name != (isAddAccessor ? "Combine" : "Remove")) return false; return combineMethod.DeclaringType.FullName == "System.Delegate"; } EventDeclaration TransformAutomaticEvents(CustomEventDeclaration ev) { Match m1 = automaticEventPatternV4.Match(ev.AddAccessor); if (!CheckAutomaticEventV4Match(m1, ev, true)) return null; Match m2 = automaticEventPatternV4.Match(ev.RemoveAccessor); if (!CheckAutomaticEventV4Match(m2, ev, false)) return null; EventDeclaration ed = new EventDeclaration(); ev.Attributes.MoveTo(ed.Attributes); foreach (var attr in ev.AddAccessor.Attributes) { attr.AttributeTarget = "method"; ed.Attributes.Add(attr.Detach()); } ed.ReturnType = ev.ReturnType.Detach(); ed.Modifiers = ev.Modifiers; ed.Variables.Add(new VariableInitializer(ev.Name)); ed.CopyAnnotationsFrom(ev); EventDefinition eventDef = ev.Annotation(); if (eventDef != null) { FieldDefinition field = eventDef.DeclaringType.Fields.FirstOrDefault(f => f.Name == ev.Name); if (field != null) { ed.AddAnnotation(field); // TODO AstBuilder.ConvertAttributes(ed, field, "field"); } } ev.ReplaceWith(ed); return ed; } #endregion #region Destructor static readonly MethodDeclaration destructorPattern = new MethodDeclaration { Attributes = { new Repeat(new AnyNode()) }, Modifiers = Modifiers.Any, ReturnType = new PrimitiveType("void"), Name = "Finalize", Body = new BlockStatement { new TryCatchStatement { TryBlock = new AnyNode("body"), FinallyBlock = new BlockStatement { new BaseReferenceExpression().Invoke("Finalize") } } } }; DestructorDeclaration TransformDestructor(MethodDeclaration methodDef) { Match m = destructorPattern.Match(methodDef); if (m.Success) { DestructorDeclaration dd = new DestructorDeclaration(); methodDef.Attributes.MoveTo(dd.Attributes); dd.Modifiers = methodDef.Modifiers & ~(Modifiers.Protected | Modifiers.Override); dd.Body = m.Get("body").Single().Detach(); dd.Name = currentTypeDefinition?.Name; methodDef.ReplaceWith(dd); return dd; } return null; } #endregion #region Try-Catch-Finally static readonly TryCatchStatement tryCatchFinallyPattern = new TryCatchStatement { TryBlock = new BlockStatement { new TryCatchStatement { TryBlock = new AnyNode(), CatchClauses = { new Repeat(new AnyNode()) } } }, FinallyBlock = new AnyNode() }; /// /// Simplify nested 'try { try {} catch {} } finally {}'. /// This transformation must run after the using/lock tranformations. /// TryCatchStatement TransformTryCatchFinally(TryCatchStatement tryFinally) { if (tryCatchFinallyPattern.IsMatch(tryFinally)) { TryCatchStatement tryCatch = (TryCatchStatement)tryFinally.TryBlock.Statements.Single(); tryFinally.TryBlock = tryCatch.TryBlock.Detach(); tryCatch.CatchClauses.MoveTo(tryFinally.CatchClauses); } // Since the tryFinally instance is not changed, we can continue in the visitor as usual, so return null return null; } #endregion #region Simplify cascading if-else-if statements static readonly IfElseStatement cascadingIfElsePattern = new IfElseStatement { Condition = new AnyNode(), TrueStatement = new AnyNode(), FalseStatement = new BlockStatement { Statements = { new NamedNode( "nestedIfStatement", new IfElseStatement { Condition = new AnyNode(), TrueStatement = new AnyNode(), FalseStatement = new OptionalNode(new AnyNode()) } ) } } }; AstNode SimplifyCascadingIfElseStatements(IfElseStatement node) { Match m = cascadingIfElsePattern.Match(node); if (m.Success) { IfElseStatement elseIf = m.Get("nestedIfStatement").Single(); node.FalseStatement = elseIf.Detach(); } return null; } /// /// Use associativity of logic operators to avoid parentheses. /// public override AstNode VisitBinaryOperatorExpression(BinaryOperatorExpression boe1) { switch (boe1.Operator) { case BinaryOperatorType.ConditionalAnd: case BinaryOperatorType.ConditionalOr: // a && (b && c) ==> (a && b) && c var boe2 = boe1.Right as BinaryOperatorExpression; if (boe2 != null && boe2.Operator == boe1.Operator) { // make boe2 the parent and boe1 the child var b = boe2.Left.Detach(); boe1.ReplaceWith(boe2.Detach()); boe2.Left = boe1; boe1.Right = b; return base.VisitBinaryOperatorExpression(boe2); } break; } return base.VisitBinaryOperatorExpression(boe1); } #endregion } }