From 2892c9d50b3340322cde4f50eb45c9275d256743 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 11 Mar 2011 12:09:21 +0100 Subject: [PATCH] Implemented decompilation of 'lock'. --- .../Transforms/PatternStatementTransform.cs | 113 +++++++++++++++++- .../ReplaceMethodCallsWithOperators.cs | 2 +- ICSharpCode.Decompiler/DecompilerSettings.cs | 15 +++ ICSharpCode.Decompiler/ILAst/ILInlining.cs | 26 +++- .../ILAst/PeepholeTransform.cs | 56 ++++++++- .../ILAst/YieldReturnDecompiler.cs | 6 + ICSharpCode.Decompiler/Tests/YieldReturn.cs | 15 +++ 7 files changed, 219 insertions(+), 14 deletions(-) diff --git a/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs index 9e2dc5042..2a575d29f 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs @@ -31,6 +31,8 @@ namespace ICSharpCode.Decompiler.Ast.Transforms TransformForeach(compilationUnit); TransformFor(compilationUnit); TransformDoWhile(compilationUnit); + if (context.Settings.LockStatement) + TransformLock(compilationUnit); if (context.Settings.AutomaticProperties) TransformAutomaticProperties(compilationUnit); if (context.Settings.AutomaticEvents) @@ -300,6 +302,83 @@ namespace ICSharpCode.Decompiler.Ast.Transforms } #endregion + #region lock + static readonly AstNode lockFlagInitPattern = new VariableDeclarationStatement { + Type = new PrimitiveType("bool"), + Variables = { + new NamedNode( + "variable", + new VariableInitializer { + Initializer = new PrimitiveExpression(false) + } + ) + }}; + + static readonly AstNode lockTryCatchPattern = new TryCatchStatement { + TryBlock = new BlockStatement { + new TypePattern(typeof(System.Threading.Monitor)).ToType().Invoke( + "Enter", new AnyNode("enter"), + new DirectionExpression { + FieldDirection = FieldDirection.Ref, + Expression = new NamedNode("flag", new IdentifierExpression()) + }), + 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 NamedNode("exit", new IdentifierExpression())) + } + } + }}; + + public void TransformLock(AstNode compilationUnit) + { + foreach (AstNode node in compilationUnit.Descendants.ToArray()) { + Match m1 = lockFlagInitPattern.Match(node); + if (m1 == null) continue; + AstNode tryCatch = node.NextSibling; + while (simpleVariableDefinition.Match(tryCatch) != null) + tryCatch = tryCatch.NextSibling; + Match m2 = lockTryCatchPattern.Match(tryCatch); + if (m2 == null) continue; + if (m1.Get("variable").Single().Name == m2.Get("flag").Single().Identifier) { + Expression enter = m2.Get("enter").Single(); + IdentifierExpression exit = m2.Get("exit").Single(); + if (exit.Match(enter) == null) { + // If exit and enter are not the same, then enter must be "exit = ..." + AssignmentExpression assign = enter as AssignmentExpression; + if (assign == null) + continue; + if (exit.Match(assign.Left) == null) + continue; + enter = assign.Right; + // Remove 'exit' variable: + bool ok = false; + for (AstNode tmp = node.NextSibling; tmp != tryCatch; tmp = tmp.NextSibling) { + VariableDeclarationStatement v = (VariableDeclarationStatement)tmp; + if (v.Variables.Single().Name == exit.Identifier) { + ok = true; + v.Remove(); + break; + } + } + if (!ok) + continue; + } + // transform the code into a lock statement: + LockStatement l = new LockStatement(); + l.Expression = enter.Detach(); + l.EmbeddedStatement = ((TryCatchStatement)tryCatch).TryBlock.Detach(); + ((BlockStatement)l.EmbeddedStatement).Statements.First().Remove(); // Remove 'Enter()' call + tryCatch.ReplaceWith(l); + node.Remove(); // remove flag variable + } + } + } + #endregion + #region Automatic Properties static readonly PropertyDeclaration automaticPropertyPattern = new PropertyDeclaration { Attributes = { new Repeat(new AnyNode()) }, @@ -391,7 +470,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms }}, new AssignmentExpression { Left = new IdentifierExpressionBackreference("var1"), - Right = new AnyNode("Interlocked").ToType().Invoke( + Right = new TypePattern(typeof(System.Threading.Interlocked)).ToType().Invoke( "CompareExchange", new AstType[] { new Backreference("type") }, // type argument new Expression[] { // arguments @@ -419,10 +498,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms var combineMethod = m.Get("delegateCombine").Single().Parent.Annotation(); if (combineMethod == null || combineMethod.Name != (isAddAccessor ? "Combine" : "Remove")) return false; - if (combineMethod.DeclaringType.FullName != "System.Delegate") - return false; - var ice = m.Get("Interlocked").Single().Annotation(); - return ice != null && ice.FullName == "System.Threading.Interlocked"; + return combineMethod.DeclaringType.FullName == "System.Delegate"; } void TransformAutomaticEvents(AstNode compilationUnit) @@ -454,5 +530,32 @@ namespace ICSharpCode.Decompiler.Ast.Transforms } } #endregion + + #region Pattern Matching Helpers + sealed class TypePattern : Pattern + { + readonly string ns; + readonly string name; + + public TypePattern(Type type) + { + this.ns = type.Namespace; + this.name = type.Name; + } + + protected override bool DoMatch(AstNode other, Match match) + { + if (other == null) + return false; + TypeReference tr = other.Annotation(); + return tr != null && tr.Namespace == ns && tr.Name == name; + } + + public override S AcceptVisitor(IAstVisitor visitor, T data) + { + throw new NotImplementedException(); + } + } + #endregion } } diff --git a/ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs b/ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs index 274a41c27..23662777f 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs @@ -205,7 +205,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms return null; } - bool IsWithoutSideEffects(Expression left) + static bool IsWithoutSideEffects(Expression left) { if (left is ThisReferenceExpression) return true; diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 82e6797b6..38f928936 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -101,6 +101,21 @@ namespace ICSharpCode.Decompiler } } + bool lockStatement = true; + + /// + /// Decompile lock statements. + /// + public bool LockStatement { + get { return lockStatement; } + set { + if (lockStatement != value) { + lockStatement = value; + OnPropertyChanged("LockStatement"); + } + } + } + public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) diff --git a/ICSharpCode.Decompiler/ILAst/ILInlining.cs b/ICSharpCode.Decompiler/ILAst/ILInlining.cs index 9c80726ea..753607453 100644 --- a/ICSharpCode.Decompiler/ILAst/ILInlining.cs +++ b/ICSharpCode.Decompiler/ILAst/ILInlining.cs @@ -15,8 +15,8 @@ namespace ICSharpCode.Decompiler.ILAst public class ILInlining { readonly ILBlock method; - Dictionary numStloc = new Dictionary(); - Dictionary numLdloc = new Dictionary(); + internal Dictionary numStloc = new Dictionary(); + internal Dictionary numLdloc = new Dictionary(); Dictionary numLdloca = new Dictionary(); Dictionary numStarg = new Dictionary(); @@ -64,7 +64,7 @@ namespace ICSharpCode.Decompiler.ILAst ILExpression nextExpr = body[i + 1] as ILExpression; ILVariable locVar; ILExpression expr; - if (body[i].Match(ILCode.Stloc, out locVar, out expr) && InlineIfPossible(block, i, aggressive: false)) { + if (body[i].Match(ILCode.Stloc, out locVar, out expr) && InlineOneIfPossible(block, i, aggressive: false)) { i = Math.Max(0, i - 1); // Go back one step } else { i++; @@ -85,7 +85,7 @@ namespace ICSharpCode.Decompiler.ILAst ILExpression expr = block.Body[pos] as ILExpression; if (expr == null || expr.Code != ILCode.Stloc) break; - if (InlineIfPossible(block, pos, aggressive)) + if (InlineOneIfPossible(block, pos, aggressive)) count++; else break; @@ -93,10 +93,26 @@ namespace ICSharpCode.Decompiler.ILAst return count; } + /// + /// Aggressively inlines the stloc instruction at block.Body[pos] into the next instruction, if possible. + /// If inlining was possible; we will continue to inline (non-aggressively) into the the combined instruction. + /// + /// + /// After the operation, pos will point to the new combined instruction. + /// + public bool InlineIfPossible(ILBlock block, ref int pos) + { + if (InlineOneIfPossible(block, pos, true)) { + pos -= InlineInto(block, pos, false); + return true; + } + return false; + } + /// /// Inlines the stloc instruction at block.Body[pos] into the next instruction, if possible. /// - public bool InlineIfPossible(ILBlock block, int pos, bool aggressive) + public bool InlineOneIfPossible(ILBlock block, int pos, bool aggressive) { ILVariable v; ILExpression inlinedExpression; diff --git a/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs index 30c4eccff..3183346da 100644 --- a/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs +++ b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs @@ -27,7 +27,8 @@ namespace ICSharpCode.Decompiler.ILAst PeepholeTransform[] blockTransforms = { ArrayInitializers.Transform(method), - transforms.CachedDelegateInitialization + transforms.CachedDelegateInitialization, + transforms.MakeAssignmentExpression }; Func[] exprTransforms = { HandleDecimalConstants, @@ -49,8 +50,8 @@ namespace ICSharpCode.Decompiler.ILAst block.Body[i] = expr; if (modified) { ILInlining inlining = new ILInlining(method); - if (inlining.InlineIfPossible(block, i, aggressive: true)) { - i -= inlining.InlineInto(block, i, aggressive: false) - 1; + if (inlining.InlineIfPossible(block, ref i)) { + i++; // retry all transforms on the new combined instruction continue; } } @@ -234,5 +235,54 @@ namespace ICSharpCode.Decompiler.ILAst } } #endregion + + #region MakeAssignmentExpression + void MakeAssignmentExpression(ILBlock block, ref int i) + { + // expr_44 = ... + // stloc(v, expr_44) + // -> + // expr_44 = stloc(v, ...)) + ILVariable exprVar; + ILExpression initializer; + if (!(block.Body[i].Match(ILCode.Stloc, out exprVar, out initializer) && exprVar.IsGenerated)) + return; + ILExpression stloc1 = block.Body.ElementAtOrDefault(i + 1) as ILExpression; + if (!(stloc1 != null && stloc1.Code == ILCode.Stloc && stloc1.Arguments[0].Code == ILCode.Ldloc && stloc1.Arguments[0].Operand == exprVar)) + return; + + ILInlining inlining; + ILExpression stloc2 = block.Body.ElementAtOrDefault(i + 2) as ILExpression; + if (stloc2 != null && stloc2.Code == ILCode.Stloc && stloc2.Arguments[0].Code == ILCode.Ldloc && stloc2.Arguments[0].Operand == exprVar) { + // expr_44 = ... + // stloc(v1, expr_44) + // stloc(v2, expr_44) + // -> + // stloc(v1, stloc(v2, ...)) + inlining = new ILInlining(method); + if (inlining.numLdloc.GetOrDefault(exprVar) == 2 && inlining.numStloc.GetOrDefault(exprVar) == 1) { + block.Body.RemoveAt(i + 2); // remove stloc2 + block.Body.RemoveAt(i); // remove expr = ... + stloc1.Arguments[0] = stloc2; + stloc2.Arguments[0] = initializer; + + if (inlining.InlineIfPossible(block, ref i)) { + i++; // retry transformations on the new combined instruction + } + return; + } + } + + + block.Body.RemoveAt(i + 1); // remove stloc + stloc1.Arguments[0] = initializer; + ((ILExpression)block.Body[i]).Arguments[0] = stloc1; + + inlining = new ILInlining(method); + if (inlining.InlineIfPossible(block, ref i)) { + i++; // retry transformations on the new combined instruction + } + } + #endregion } } diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs index b7e3f6d29..c99d28450 100644 --- a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -60,6 +60,12 @@ namespace ICSharpCode.Decompiler.ILAst method.Body.Clear(); method.EntryGoto = null; method.Body.AddRange(yrd.newBody); + + // Repeat the inlining/copy propagation optimization because the conversion of field access + // to local variables can open up additional inlining possibilities. + ILInlining inlining = new ILInlining(method); + inlining.InlineAllVariables(); + inlining.CopyPropagation(); } void Run() diff --git a/ICSharpCode.Decompiler/Tests/YieldReturn.cs b/ICSharpCode.Decompiler/Tests/YieldReturn.cs index f7c7f73f6..21a2163ce 100644 --- a/ICSharpCode.Decompiler/Tests/YieldReturn.cs +++ b/ICSharpCode.Decompiler/Tests/YieldReturn.cs @@ -31,6 +31,21 @@ public static class YieldReturn yield return 2; } + public static IEnumerable YieldReturnInLock1(object o) + { + lock (o) { + yield return 1; + } + } + + public static IEnumerable YieldReturnInLock2(object o) + { + lock (o) { + yield return 1; + o = null; + yield return 2; + } + } public static IEnumerable YieldReturnWithNestedTryFinally(bool breakInMiddle) {