// 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; using ICSharpCode.Decompiler.Semantics; 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; } if (context.Settings.AutomaticProperties) { result = ReplaceBackingFieldUsage(expressionStatement); if (result != null) return result; } if (context.Settings.AutomaticEvents) { result = ReplaceEventFieldAnnotation(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 { new InvocationExpression(new MemberReferenceExpression(identifier, "Dispose")), new InvocationExpression(new MemberReferenceExpression(new CastExpression(new TypePattern(typeof(IDisposable)), identifier.Clone()), "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) { // Conditions: // 1. CaptureScope of the resource-variable must be either null or the BlockContainer of the using block. // 2. The variable must not be used outside of the block. // 3. The variable is only assigned once, right before the try-block. 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; } if (variable.StoreCount > 1 || !variable.Type.GetAllBaseTypes().Any(t => t.IsKnownType(KnownTypeCode.IDisposable))) 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 variable.Kind = IL.VariableKind.UsingLocal; usingStatement.ResourceAcquisition = new VariableDeclarationStatement { Type = variable.Type.ContainsAnonymousType() ? new SimpleType("var") : context.TypeSystemAstBuilder.ConvertType(variable.Type), Variables = { new VariableInitializer { Name = variableName, Initializer = m1.Get("initializer").Single().Detach() }.CopyAnnotationsFrom(node.Expression) .WithILVariable(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) 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 InvocationExpression(new MemberReferenceExpression(new AnyNode("collection").ToExpression(), "GetEnumerator")) } ) } }, EmbeddedStatement = new BlockStatement { new Repeat( new VariableDeclarationStatement { Type = new AnyNode(), Variables = { new VariableInitializer(Pattern.AnyString) } }.WithName("variablesOutsideLoop") ).ToStatement(), new WhileStatement { Condition = new InvocationExpression(new MemberReferenceExpression(new IdentifierExpressionBackreference("enumeratorVariable").ToExpression(), "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 MemberReferenceExpression(new IdentifierExpressionBackreference("enumeratorVariable").ToExpression(), "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(); var itemVar = m.Get("itemVariable").Single().GetILVariable(); WhileStatement loop = m.Get("loop").Single(); if (!VariableCanBeDeclaredInLoop(itemVar, 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()); itemVar.Kind = IL.VariableKind.ForeachLocal; ForeachStatement foreachStatement = new ForeachStatement { VariableType = itemVar.Type.ContainsAnonymousType() ? new SimpleType("var") : context.TypeSystemAstBuilder.ConvertType(itemVar.Type), VariableName = itemVar.Name, InExpression = m.Get("collection").Single().Detach(), EmbeddedStatement = newBody }.WithILVariable(itemVar); foreachStatement.CopyAnnotationsFrom(loop); 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; } static bool VariableCanBeDeclaredInLoop(IL.ILVariable itemVar, WhileStatement loop) { if (itemVar == null || !(itemVar.Kind == IL.VariableKind.Local || itemVar.Kind == IL.VariableKind.StackSlot)) { // only locals/temporaries can be converted into foreach loop variable return false; } var blockContainer = loop.Annotation(); if (!itemVar.IsSingleDefinition) { // foreach variable cannot be assigned to. // As a special case, we accept taking the address for a method call, // but only if the call is the only use, so that any mutation by the call // cannot be observed. if (!AddressUsedForSingleCall(itemVar, blockContainer)) { return false; } } if (itemVar.CaptureScope != null && itemVar.CaptureScope != blockContainer) { // captured variables cannot be declared in the loop unless the loop is their capture scope return false; } return true; } static bool AddressUsedForSingleCall(IL.ILVariable v, IL.BlockContainer loop) { if (v.StoreCount == 1 && v.AddressCount == 1 && v.LoadCount == 0 && v.Type.IsReferenceType == false) { if (v.AddressInstructions[0].Parent is IL.Call call && v.AddressInstructions[0].ChildIndex == 0 && !call.Method.IsStatic) { // used as this pointer for a method call // this is OK iff the call is not within a nested loop for (var node = call.Parent; node != null; node = node.Parent) { if (node == loop) return true; else if (node is IL.BlockContainer) break; } } } return false; } #endregion #region foreach (non-generic) ExpressionStatement getEnumeratorPattern = new ExpressionStatement( new AssignmentExpression( new NamedNode("left", new IdentifierExpression(Pattern.AnyString)), new InvocationExpression(new MemberReferenceExpression(new AnyNode("collection").ToExpression(), "GetEnumerator")) )); TryCatchStatement nonGenericForeachPattern = new TryCatchStatement { TryBlock = new BlockStatement { new WhileStatement { Condition = new InvocationExpression(new MemberReferenceExpression(new IdentifierExpression(Pattern.AnyString).WithName("enumerator"), "MoveNext")), EmbeddedStatement = new BlockStatement { new AssignmentExpression( new IdentifierExpression(Pattern.AnyString).WithName("itemVar"), new Choice { new MemberReferenceExpression(new Backreference("enumerator").ToExpression(), "Current"), new CastExpression { Type = new AnyNode("castType"), Expression = new MemberReferenceExpression(new Backreference("enumerator").ToExpression(), "Current") } } ), new Repeat(new AnyNode("stmt")).ToStatement() } }.WithName("loop") }, FinallyBlock = new BlockStatement { new AssignmentExpression( new IdentifierExpression(Pattern.AnyString).WithName("disposable"), new AsExpression(new Backreference("enumerator").ToExpression(), new TypePattern(typeof(IDisposable))) ), new IfElseStatement { Condition = new BinaryOperatorExpression { Left = new Backreference("disposable"), Operator = BinaryOperatorType.InEquality, Right = new NullReferenceExpression() }, TrueStatement = new BlockStatement { new InvocationExpression(new MemberReferenceExpression(new Backreference("disposable").ToExpression(), "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(); var itemVar = m2.Get("itemVar").Single().GetILVariable(); 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; if (!VariableCanBeDeclaredInLoop(itemVar, loop)) return null; itemVar.Kind = IL.VariableKind.ForeachLocal; ForeachStatement foreachStatement = new ForeachStatement { VariableType = itemVar.Type.ContainsAnonymousType() ? new SimpleType("var") : context.TypeSystemAstBuilder.ConvertType(itemVar.Type), VariableName = itemVar.Name, }.WithILVariable(itemVar); BlockStatement body = new BlockStatement(); foreachStatement.EmbeddedStatement = body; foreachStatement.CopyAnnotationsFrom(loop); ((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; var variableName = m1.Get("variable").Single().Identifier; AstNode next = node.NextSibling; if (next is ForStatement forStatement) { if ((forStatement.Iterators.FirstOrDefault() is ExpressionStatement stmt && stmt.Expression is AssignmentExpression assign && variableName == assign.Left.ToString()) || (forStatement.Condition is BinaryOperatorExpression cond && variableName == cond.Left.ToString())) { node.Remove(); forStatement.InsertChildAfter(null, node, ForStatement.InitializerRole); return forStatement; } } Match m3 = forPattern.Match(next); if (!m3.Success) return null; // ensure the variable in the for pattern is the same as in the declaration if (variableName != m3.Get("ident").Single().Identifier) return null; WhileStatement loop = (WhileStatement)next; node.Remove(); BlockStatement newBody = new BlockStatement(); foreach (Statement stmt in m3.Get("statement")) newBody.Add(stmt.Detach()); forStatement = new ForStatement(); forStatement.CopyAnnotationsFrom(loop); forStatement.Initializers.Add(node); forStatement.Condition = loop.Condition.Detach(); forStatement.Iterators.Add(m3.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(); doLoop.CopyAnnotationsFrom(whileLoop); 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 InvocationExpression(new MemberReferenceExpression(new TypeReferenceExpression(new TypePattern(typeof(System.Threading.Monitor)).ToType()), "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 InvocationExpression(new MemberReferenceExpression(new TypeReferenceExpression(new TypePattern(typeof(System.Threading.Monitor)).ToType()), "Exit"), new AnyNode("exit")) } } }}; static readonly AstNode oldMonitorCallPattern = new ExpressionStatement( new InvocationExpression(new MemberReferenceExpression(new TypeReferenceExpression(new TypePattern(typeof(System.Threading.Monitor)).ToType()), "Enter"), new AnyNode("enter")) ); static readonly AstNode oldLockTryCatchPattern = new TryCatchStatement { TryBlock = new BlockStatement { new Repeat(new AnyNode()).ToStatement() }, FinallyBlock = new BlockStatement { new InvocationExpression(new MemberReferenceExpression(new TypeReferenceExpression(new TypePattern(typeof(System.Threading.Monitor)).ToType()), "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 InvocationExpression(new MemberReferenceExpression(new Backreference("cachedDict").ToExpression(), "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") } }} }; static readonly PropertyDeclaration automaticReadonlyPropertyPattern = 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") } } } }; PropertyDeclaration TransformAutomaticProperties(PropertyDeclaration property) { PropertyDefinition cecilProperty = context.TypeSystem.GetCecil(property.GetSymbol() as IProperty) as PropertyDefinition; if (cecilProperty == null || cecilProperty.GetMethod == null) return null; if (!cecilProperty.GetMethod.IsCompilerGenerated() && (cecilProperty.SetMethod?.IsCompilerGenerated() == false)) return null; IField fieldInfo = null; Match m = automaticPropertyPattern.Match(property); if (m.Success) { fieldInfo = m.Get("fieldReference").Single().GetSymbol() as IField; } else { Match m2 = automaticReadonlyPropertyPattern.Match(property); if (m2.Success) { fieldInfo = m2.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(); } } ExpressionStatement ReplaceBackingFieldUsage(ExpressionStatement expressionStatement) { foreach (var identifier in expressionStatement.Descendants.OfType()) { if (identifier.Name.StartsWith("<") && identifier.Name.EndsWith(">k__BackingField")) { var parent = identifier.Parent; var mrr = parent.Annotation(); var field = mrr?.Member as IField; if (field != null && field.IsCompilerGenerated()) { var propertyName = identifier.Name.Substring(1, identifier.Name.Length - 1 - ">k__BackingField".Length); var property = field.DeclaringTypeDefinition.GetProperties(p => p.Name == propertyName, GetMemberOptions.IgnoreInheritedMembers).FirstOrDefault(); if (property != null) { identifier.ReplaceWith(Identifier.Create(propertyName)); parent.RemoveAnnotations(); parent.AddAnnotation(new MemberResolveResult(mrr.TargetResult, property)); } } } } return null; } ExpressionStatement ReplaceEventFieldAnnotation(ExpressionStatement expressionStatement) { foreach (var identifier in expressionStatement.Descendants.OfType()) { var parent = identifier.Parent; var mrr = parent.Annotation(); var field = mrr?.Member as IField; if (field == null) continue; var @event = field.DeclaringType.GetEvents(ev => ev.Name == field.Name, GetMemberOptions.IgnoreInheritedMembers).SingleOrDefault(); if (@event != null) { parent.RemoveAnnotations(); parent.AddAnnotation(new MemberResolveResult(mrr.TargetResult, @event)); } } return null; } #endregion #region Automatic Events static readonly Accessor automaticEventPatternV4 = new Accessor { Attributes = { new Repeat(new AnyNode()) }, Body = new BlockStatement { 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 WhileStatement { Condition = new PrimitiveExpression(true), 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 CastExpression(new AnyNode("type"), new InvocationExpression(new AnyNode("delegateCombine").ToExpression(), new CastExpression(new TypePattern(typeof(System.Delegate)), new IdentifierExpressionBackreference("var2")), new CastExpression(new TypePattern(typeof(System.Delegate)), new IdentifierExpression("value")) )) }, new AssignmentExpression { Left = new IdentifierExpressionBackreference("var1"), Right = new InvocationExpression(new MemberReferenceExpression(new TypeReferenceExpression(new TypePattern(typeof(System.Threading.Interlocked)).ToType()), "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") } )}, new IfElseStatement { Condition = new BinaryOperatorExpression { Left = new CastExpression(new TypePattern(typeof(object)), new IdentifierExpressionBackreference("var1")), Operator = BinaryOperatorType.Equality, Right = new IdentifierExpressionBackreference("var2") }, TrueStatement = new BreakStatement() } } } }}; 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.GetSymbol() as IMethod; if (combineMethod == null || combineMethod.Name != (isAddAccessor ? "Combine" : "Remove")) return false; return combineMethod.DeclaringType.FullName == "System.Delegate"; } static readonly string[] attributeTypesToRemoveFromAutoEvents = new[] { "System.Runtime.CompilerServices.CompilerGeneratedAttribute", "System.Diagnostics.DebuggerBrowsableAttribute" }; 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; RemoveCompilerGeneratedAttribute(ev.AddAccessor.Attributes); 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); IEvent eventDef = ev.GetSymbol() as IEvent; if (eventDef != null) { IField field = eventDef.DeclaringType.GetFields(f => f.Name == ev.Name, GetMemberOptions.IgnoreInheritedMembers).SingleOrDefault(); if (field != null) { ed.AddAnnotation(field); var attributes = field.Attributes .Where(a => !attributeTypesToRemoveFromAutoEvents.Any(t => t == a.AttributeType.FullName)) .Select(context.TypeSystemAstBuilder.ConvertAttribute).ToArray(); if (attributes.Length > 0) { var section = new AttributeSection { AttributeTarget = "field" }; section.Attributes.AddRange(attributes); ed.Attributes.Add(section); } } } 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 InvocationExpression(new MemberReferenceExpression(new BaseReferenceExpression(), "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 } }