diff --git a/Debugger/ILSpy.Debugger/Models/TreeModel/ExpressionNode.cs b/Debugger/ILSpy.Debugger/Models/TreeModel/ExpressionNode.cs index 3a9fd740b..a220e6e07 100644 --- a/Debugger/ILSpy.Debugger/Models/TreeModel/ExpressionNode.cs +++ b/Debugger/ILSpy.Debugger/Models/TreeModel/ExpressionNode.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; +using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Windows.Media; @@ -159,9 +160,9 @@ namespace ILSpy.Debugger.Models.TreeModel } // get local variable index - List list; + IEnumerable list; if (ILAstBuilder.MemberLocalVariables.TryGetValue(token, out list)) { - var variable = list.Find(v => v.Name == targetName); + var variable = list.FirstOrDefault(v => v.Name == targetName); if (variable != null) { if (expression is MemberReferenceExpression) { var memberExpression = (MemberReferenceExpression)expression; diff --git a/ICSharpCode.Decompiler/Ast/AstBuilder.cs b/ICSharpCode.Decompiler/Ast/AstBuilder.cs index 327e5292c..53a5a4812 100644 --- a/ICSharpCode.Decompiler/Ast/AstBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstBuilder.cs @@ -33,19 +33,27 @@ namespace ICSharpCode.Decompiler.Ast this.context = context; } - public static bool MemberIsHidden(MemberReference member) + public static bool MemberIsHidden(MemberReference member, DecompilerSettings settings) { MethodDefinition method = member as MethodDefinition; - if (method != null && (method.IsGetter || method.IsSetter || method.IsAddOn || method.IsRemoveOn)) - return true; - if (method != null && method.Name.StartsWith("<", StringComparison.Ordinal) && method.IsCompilerGenerated()) - return true; + if (method != null) { + if (method.IsGetter || method.IsSetter || method.IsAddOn || method.IsRemoveOn) + return true; + if (settings.AnonymousMethods && method.Name.StartsWith("<", StringComparison.Ordinal) && method.IsCompilerGenerated()) + return true; + } TypeDefinition type = member as TypeDefinition; - if (type != null && type.DeclaringType != null && type.Name.StartsWith("<>c__DisplayClass", StringComparison.Ordinal) && type.IsCompilerGenerated()) - return true; + if (type != null && type.DeclaringType != null) { + if (settings.AnonymousMethods && type.Name.StartsWith("<>c__DisplayClass", StringComparison.Ordinal) && type.IsCompilerGenerated()) + return true; + if (settings.YieldReturn && YieldReturnDecompiler.IsCompilerGeneratorEnumerator(type)) + return true; + } FieldDefinition field = member as FieldDefinition; - if (field != null && field.Name.StartsWith("CS$<>", StringComparison.Ordinal) && field.IsCompilerGenerated()) - return true; + if (field != null) { + if (settings.AnonymousMethods && field.Name.StartsWith("CS$<>", StringComparison.Ordinal) && field.IsCompilerGenerated()) + return true; + } return false; } @@ -150,6 +158,8 @@ namespace ICSharpCode.Decompiler.Ast } // create type + TypeDefinition oldCurrentType = context.CurrentType; + context.CurrentType = typeDef; TypeDeclaration astType = new TypeDeclaration(); astType.AddAnnotation(typeDef); astType.Modifiers = ConvertModifiers(typeDef); @@ -176,7 +186,7 @@ namespace ICSharpCode.Decompiler.Ast // Nested types foreach(TypeDefinition nestedTypeDef in typeDef.NestedTypes) { - if (MemberIsHidden(nestedTypeDef)) + if (MemberIsHidden(nestedTypeDef, context.Settings)) continue; astType.AddChild(CreateType(nestedTypeDef), TypeDeclaration.MemberRole); } @@ -215,6 +225,7 @@ namespace ICSharpCode.Decompiler.Ast } ConvertAttributes(astType, typeDef); + context.CurrentType = oldCurrentType; return astType; } @@ -492,7 +503,7 @@ namespace ICSharpCode.Decompiler.Ast { // Add fields foreach(FieldDefinition fieldDef in typeDef.Fields) { - if (MemberIsHidden(fieldDef)) continue; + if (MemberIsHidden(fieldDef, context.Settings)) continue; astType.AddChild(CreateField(fieldDef), TypeDeclaration.MemberRole); } @@ -515,7 +526,7 @@ namespace ICSharpCode.Decompiler.Ast // Add methods foreach(MethodDefinition methodDef in typeDef.Methods) { - if (methodDef.IsConstructor || MemberIsHidden(methodDef)) continue; + if (methodDef.IsConstructor || MemberIsHidden(methodDef, context.Settings)) continue; astType.AddChild(CreateMethod(methodDef), TypeDeclaration.MemberRole); } diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index 01f86482b..9b56a192b 100644 --- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -63,7 +63,8 @@ namespace ICSharpCode.Decompiler.Ast bodyGraph.Optimize(context, ilMethod); context.CancellationToken.ThrowIfCancellationRequested(); - NameVariables.AssignNamesToVariables(methodDef.Parameters.Select(p => p.Name), astBuilder.Variables, ilMethod); + var allVariables = ilMethod.GetSelfAndChildrenRecursive().Select(e => e.Operand as ILVariable).Where(v => v != null && !v.IsGenerated).Distinct(); + NameVariables.AssignNamesToVariables(methodDef.Parameters.Select(p => p.Name), allVariables, ilMethod); context.CancellationToken.ThrowIfCancellationRequested(); Ast.BlockStatement astBlock = TransformBlock(ilMethod); @@ -75,7 +76,7 @@ namespace ICSharpCode.Decompiler.Ast // store the variables - used for debugger int token = methodDef.MetadataToken.ToInt32(); ILAstBuilder.MemberLocalVariables.AddOrUpdate( - token, astBuilder.Variables, (key, oldValue) => astBuilder.Variables); + token, allVariables, (key, oldValue) => allVariables); return astBlock; } @@ -499,6 +500,10 @@ namespace ICSharpCode.Decompiler.Ast case ILCode.Throw: return new Ast.ThrowStatement { Expression = arg1 }; case ILCode.Unaligned: return InlineAssembly(byteCode, args); case ILCode.Volatile: return InlineAssembly(byteCode, args); + case ILCode.YieldBreak: + return new Ast.YieldBreakStatement(); + case ILCode.YieldReturn: + return new Ast.YieldStatement { Expression = arg1 }; default: throw new Exception("Unknown OpCode: " + byteCode.Code); } } diff --git a/ICSharpCode.Decompiler/Ast/DecompilerContext.cs b/ICSharpCode.Decompiler/Ast/DecompilerContext.cs index 7ea5c0b02..69e098531 100644 --- a/ICSharpCode.Decompiler/Ast/DecompilerContext.cs +++ b/ICSharpCode.Decompiler/Ast/DecompilerContext.cs @@ -12,6 +12,7 @@ namespace ICSharpCode.Decompiler public CancellationToken CancellationToken; public TypeDefinition CurrentType; public MethodDefinition CurrentMethod; + public DecompilerSettings Settings = new DecompilerSettings(); public DecompilerContext Clone() { diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs index c0fcabab3..7684b1702 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs @@ -105,6 +105,9 @@ namespace ICSharpCode.Decompiler.Ast.Transforms bool HandleAnonymousMethod(ObjectCreateExpression objectCreateExpression, Expression target, MethodReference methodRef) { + if (!context.Settings.AnonymousMethods) + return false; // anonymous method decompilation is disabled + // Anonymous methods are defined in the same assembly, so there's no need to Resolve(). MethodDefinition method = methodRef as MethodDefinition; if (!IsAnonymousMethod(context, method)) diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs new file mode 100644 index 000000000..53736311a --- /dev/null +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -0,0 +1,55 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using System.ComponentModel; + +namespace ICSharpCode.Decompiler +{ + /// + /// Settings for the decompiler. + /// + public class DecompilerSettings : INotifyPropertyChanged + { + bool anonymousMethods = true; + + /// + /// Decompile anonymous methods/lambdas. + /// + public bool AnonymousMethods { + get { return anonymousMethods; } + set { + if (anonymousMethods != value) { + anonymousMethods = value; + OnPropertyChanged("AnonymousMethods"); + } + } + } + + bool yieldReturn = true; + + /// + /// Decompile enumerators. + /// + public bool YieldReturn { + get { return yieldReturn; } + set { + if (yieldReturn != value) { + yieldReturn = value; + OnPropertyChanged("YieldReturn"); + } + } + } + + public event EventHandler YieldReturnChanged; + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged(string propertyName) + { + if (PropertyChanged != null) { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 6bf96607c..7e6f812aa 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -68,6 +68,7 @@ + @@ -89,6 +90,7 @@ + @@ -98,6 +100,7 @@ + diff --git a/ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs b/ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs new file mode 100644 index 000000000..e35bb868d --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs @@ -0,0 +1,117 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace ICSharpCode.Decompiler.ILAst +{ + /// + /// Dictionary with default values. + /// + sealed class DefaultDictionary : IDictionary + { + readonly IDictionary dict; + readonly Func defaultProvider; + + public DefaultDictionary(TValue defaultValue, IDictionary dictionary = null) + : this(key => defaultValue, dictionary) + { + } + + public DefaultDictionary(Func defaultProvider = null, IDictionary dictionary = null) + { + this.dict = dictionary ?? new Dictionary(); + this.defaultProvider = defaultProvider ?? (key => default(TValue)); + } + + public TValue this[TKey key] { + get { + TValue val; + if (dict.TryGetValue(key, out val)) + return val; + else + return dict[key] = defaultProvider(key); + } + set { + dict[key] = value; + } + } + + public ICollection Keys { + get { return dict.Keys; } + } + + public ICollection Values { + get { return dict.Values; } + } + + public int Count { + get { return dict.Count; } + } + + bool ICollection>.IsReadOnly { + get { return false; } + } + + public bool ContainsKey(TKey key) + { + return dict.ContainsKey(key); + } + + public void Add(TKey key, TValue value) + { + dict.Add(key, value); + } + + public bool Remove(TKey key) + { + return dict.Remove(key); + } + + public bool TryGetValue(TKey key, out TValue value) + { + return dict.TryGetValue(key, out value); + } + + void ICollection>.Add(KeyValuePair item) + { + dict.Add(item); + } + + public void Clear() + { + dict.Clear(); + } + + bool ICollection>.Contains(KeyValuePair item) + { + return dict.Contains(item); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + dict.CopyTo(array, arrayIndex); + } + + bool ICollection>.Remove(KeyValuePair item) + { + return dict.Remove(item); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return dict.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return dict.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs index 0dd715afb..3c972d8bd 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs @@ -15,7 +15,7 @@ namespace ICSharpCode.Decompiler.ILAst /// /// /// - public static ConcurrentDictionary> MemberLocalVariables = new ConcurrentDictionary>(); + public static ConcurrentDictionary> MemberLocalVariables = new ConcurrentDictionary>(); static ByteCode[] EmptyByteCodeArray = new ByteCode[] {}; @@ -214,8 +214,6 @@ namespace ICSharpCode.Decompiler.ILAst // Virtual instructions to load exception on stack Dictionary ldexceptions = new Dictionary(); - public List Variables; - public List Build(MethodDefinition methodDef, bool optimize) { this.methodDef = methodDef; @@ -456,7 +454,6 @@ namespace ICSharpCode.Decompiler.ILAst { if (optimize) { int varCount = methodDef.Body.Variables.Count; - this.Variables = new List(varCount * 2); for(int variableIndex = 0; variableIndex < varCount; variableIndex++) { // Find all stores and loads for this variable @@ -528,16 +525,13 @@ namespace ICSharpCode.Decompiler.ILAst load.Operand = newVar.Variable; } } - - // Record new variables to global list - this.Variables.AddRange(newVars.Select(v => v.Variable)); } } else { - this.Variables = methodDef.Body.Variables.Select(v => new ILVariable() { Name = string.IsNullOrEmpty(v.Name) ? "var_" + v.Index : v.Name, Type = v.VariableType, OriginalVariable = v }).ToList(); + var variables = methodDef.Body.Variables.Select(v => new ILVariable() { Name = string.IsNullOrEmpty(v.Name) ? "var_" + v.Index : v.Name, Type = v.VariableType, OriginalVariable = v }).ToList(); foreach(ByteCode byteCode in body) { if (byteCode.Code == ILCode.Ldloc || byteCode.Code == ILCode.Stloc || byteCode.Code == ILCode.Ldloca) { int index = ((VariableDefinition)byteCode.Operand).Index; - byteCode.Operand = this.Variables[index]; + byteCode.Operand = variables[index]; } } } @@ -616,9 +610,10 @@ namespace ICSharpCode.Decompiler.ILAst tryCatchBlock.CatchBlocks.Add(catchBlock); } else if (eh.HandlerType == ExceptionHandlerType.Finally) { tryCatchBlock.FinallyBlock = new ILBlock(handlerAst); - // TODO: ldexception + } else if (eh.HandlerType == ExceptionHandlerType.Fault) { + tryCatchBlock.FaultBlock = new ILBlock(handlerAst); } else { - // TODO + // TODO: ExceptionHandlerType.Filter } } @@ -710,9 +705,6 @@ namespace ICSharpCode.Decompiler.ILAst currExpr.Arguments[0].ILRanges.AddRange(currExpr.ILRanges); currExpr.Arguments[0].ILRanges.AddRange(nextExpr.Arguments[j].ILRanges); - // Remove from global list, if present - this.Variables.Remove((ILVariable)arg.Operand); - ast.RemoveAt(i); nextExpr.Arguments[j] = currExpr.Arguments[0]; // Inline the stloc body i -= 2; // Try the same index again diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index 6aa3f352c..fa0a6b15f 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -12,6 +12,7 @@ namespace ICSharpCode.Decompiler.ILAst public enum ILAstOptimizationStep { ReduceBranchInstructionSet, + YieldReturn, SplitToMovableBlocks, PeepholeOptimizations, FindLoops, @@ -38,6 +39,9 @@ namespace ICSharpCode.Decompiler.ILAst ReduceBranchInstructionSet(block); } + if (abortBeforeStep == ILAstOptimizationStep.YieldReturn) return; + YieldReturnDecompiler.Run(context, method); + if (abortBeforeStep == ILAstOptimizationStep.SplitToMovableBlocks) return; foreach(ILBlock block in method.GetSelfAndChildrenRecursive().ToList()) { SplitToBasicBlocks(block); @@ -106,17 +110,17 @@ namespace ICSharpCode.Decompiler.ILAst expr.Arguments.Single().ILRanges.AddRange(expr.ILRanges); expr.ILRanges.Clear(); continue; - case ILCode.__Brfalse: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, expr.Arguments.Single())); break; - case ILCode.__Beq: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Ceq, null, expr.Arguments)); break; - case ILCode.__Bne_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Ceq, null, expr.Arguments))); break; - case ILCode.__Bgt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt, null, expr.Arguments)); break; - case ILCode.__Bgt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments)); break; - case ILCode.__Ble: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt, null, expr.Arguments))); break; - case ILCode.__Ble_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments))); break; - case ILCode.__Blt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt, null, expr.Arguments)); break; - case ILCode.__Blt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt_Un, null, expr.Arguments)); break; - case ILCode.__Bge: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt, null, expr.Arguments))); break; - case ILCode.__Bge_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt_Un, null, expr.Arguments))); break; + case ILCode.__Brfalse: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, expr.Arguments.Single())); break; + case ILCode.__Beq: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Ceq, null, expr.Arguments)); break; + case ILCode.__Bne_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Ceq, null, expr.Arguments))); break; + case ILCode.__Bgt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt, null, expr.Arguments)); break; + case ILCode.__Bgt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments)); break; + case ILCode.__Ble: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt, null, expr.Arguments))); break; + case ILCode.__Ble_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments))); break; + case ILCode.__Blt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt, null, expr.Arguments)); break; + case ILCode.__Blt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt_Un, null, expr.Arguments)); break; + case ILCode.__Bge: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt, null, expr.Arguments))); break; + case ILCode.__Bge_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt_Un, null, expr.Arguments))); break; default: continue; } @@ -176,7 +180,7 @@ namespace ICSharpCode.Decompiler.ILAst lastBlock.FallthoughGoto = new ILExpression(ILCode.Br, basicBlock.EntryLabel); } } else { - basicBlock.Body.Add(currNode); + basicBlock.Body.Add(currNode); } } } @@ -226,7 +230,7 @@ namespace ICSharpCode.Decompiler.ILAst if (TrySimplifyShortCircuit(block.Body, bb)) { modified = true; continue; - } + } if (TrySimplifyTernaryOperator(block.Body, bb)) { modified = true; continue; @@ -462,7 +466,7 @@ namespace ICSharpCode.Decompiler.ILAst labelToCfNode.TryGetValue(trueLabel, out trueTarget); ControlFlowNode falseTarget; labelToCfNode.TryGetValue(falseLabel, out falseTarget); - + // If one point inside the loop and the other outside if ((!loopContents.Contains(trueTarget) && loopContents.Contains(falseTarget)) || (loopContents.Contains(trueTarget) && !loopContents.Contains(falseTarget)) ) @@ -491,36 +495,36 @@ namespace ICSharpCode.Decompiler.ILAst // Use loop to implement the condition result.Add(new ILBasicBlock() { - EntryLabel = basicBlock.EntryLabel, - Body = new List() { - new ILWhileLoop() { - Condition = condExpr, - BodyBlock = new ILBlock() { - EntryGoto = new ILExpression(ILCode.Br, trueLabel), - Body = FindLoops(loopContents, node, true) - } - }, - new ILExpression(ILCode.Br, falseLabel) - }, - FallthoughGoto = null - }); + EntryLabel = basicBlock.EntryLabel, + Body = new List() { + new ILWhileLoop() { + Condition = condExpr, + BodyBlock = new ILBlock() { + EntryGoto = new ILExpression(ILCode.Br, trueLabel), + Body = FindLoops(loopContents, node, true) + } + }, + new ILExpression(ILCode.Br, falseLabel) + }, + FallthoughGoto = null + }); } } // Fallback method: while(true) if (scope.Contains(node)) { result.Add(new ILBasicBlock() { - EntryLabel = new ILLabel() { Name = "Loop_" + (nextLabelIndex++) }, - Body = new List() { - new ILWhileLoop() { - BodyBlock = new ILBlock() { - EntryGoto = new ILExpression(ILCode.Br, basicBlock.EntryLabel), - Body = FindLoops(loopContents, node, true) - } - }, - }, - FallthoughGoto = null - }); + EntryLabel = new ILLabel() { Name = "Loop_" + (nextLabelIndex++) }, + Body = new List() { + new ILWhileLoop() { + BodyBlock = new ILBlock() { + EntryGoto = new ILExpression(ILCode.Br, basicBlock.EntryLabel), + Body = FindLoops(loopContents, node, true) + } + }, + }, + FallthoughGoto = null + }); } // Move the content into loop block @@ -639,7 +643,7 @@ namespace ICSharpCode.Decompiler.ILAst var caseBlock = new ILSwitch.CaseBlock() { EntryGoto = new ILExpression(ILCode.Br, fallLabel) }; ilSwitch.CaseBlocks.Add(caseBlock); newBB.FallthoughGoto = null; - + scope.ExceptWith(content); caseBlock.Body.AddRange(FindConditions(content, fallTarget)); // Add explicit break which should not be used by default, but the goto removal might decide to use it @@ -667,9 +671,9 @@ namespace ICSharpCode.Decompiler.ILAst FalseBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, falseLabel) } }; result.Add(new ILBasicBlock() { - EntryLabel = block.EntryLabel, // Keep the entry label - Body = { ilCond } - }); + EntryLabel = block.EntryLabel, // Keep the entry label + Body = { ilCond } + }); // Remove the item immediately so that it is not picked up as content scope.RemoveOrThrow(node); @@ -742,8 +746,8 @@ namespace ICSharpCode.Decompiler.ILAst static HashSet FindLoopContent(HashSet scope, ControlFlowNode head) { - var exitNodes = head.DominanceFrontier.SelectMany(n => n.Predecessors); - HashSet agenda = new HashSet(exitNodes); + var viaBackEdges = head.Predecessors.Where(p => head.Dominates(p)); + HashSet agenda = new HashSet(viaBackEdges); HashSet result = new HashSet(); while(agenda.Count > 0) { @@ -866,6 +870,17 @@ namespace ICSharpCode.Decompiler.ILAst return false; } + public static bool Match(this ILNode node, ILCode code, out ILExpression arg) + { + List args; + if (node.Match(code, out args) && args.Count == 1) { + arg = args[0]; + return true; + } + arg = null; + return false; + } + public static bool Match(this ILNode node, ILCode code, out T operand, out List args) { ILExpression expr = node as ILExpression; @@ -882,14 +897,27 @@ namespace ICSharpCode.Decompiler.ILAst public static bool Match(this ILNode node, ILCode code, out T operand, out ILExpression arg) { List args; - if (node.Match(code, out operand, out args)) { - arg = args.Single(); + if (node.Match(code, out operand, out args) && args.Count == 1) { + arg = args[0]; return true; } arg = null; return false; } + public static bool Match(this ILNode node, ILCode code, out T operand, out ILExpression arg1, out ILExpression arg2) + { + List args; + if (node.Match(code, out operand, out args) && args.Count == 2) { + arg1 = args[0]; + arg2 = args[1]; + return true; + } + arg1 = null; + arg2 = null; + return false; + } + public static bool Match(this ILBasicBlock bb, ILCode code, out T operand, out ILExpression arg, out ILLabel fallLabel) { if (bb.Body.Count == 1) { diff --git a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs index 6eb02de94..89ad2b781 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs @@ -151,6 +151,7 @@ namespace ICSharpCode.Decompiler.ILAst public ILBlock TryBlock; public List CatchBlocks; public ILBlock FinallyBlock; + public ILBlock FaultBlock; public override IEnumerable GetChildren() { @@ -159,6 +160,8 @@ namespace ICSharpCode.Decompiler.ILAst foreach (var catchBlock in this.CatchBlocks) { yield return catchBlock; } + if (this.FaultBlock != null) + yield return this.FaultBlock; if (this.FinallyBlock != null) yield return this.FinallyBlock; } @@ -173,6 +176,13 @@ namespace ICSharpCode.Decompiler.ILAst foreach (CatchBlock block in CatchBlocks) { block.WriteTo(output); } + if (FaultBlock != null) { + output.WriteLine("fault {"); + output.Indent(); + FaultBlock.WriteTo(output); + output.Unindent(); + output.WriteLine("}"); + } if (FinallyBlock != null) { output.WriteLine("finally {"); output.Indent(); @@ -354,6 +364,13 @@ namespace ICSharpCode.Decompiler.ILAst if (Operand != null) { if (Operand is ILLabel) { output.WriteReference(((ILLabel)Operand).Name, Operand); + } else if (Operand is ILLabel[]) { + ILLabel[] labels = (ILLabel[])Operand; + for (int i = 0; i < labels.Length; i++) { + if (i > 0) + output.Write(", "); + output.WriteReference(labels[i].Name, labels[i]); + } } else if (Operand is MethodReference) { MethodReference method = (MethodReference)Operand; method.DeclaringType.WriteTo(output, true, true); diff --git a/ICSharpCode.Decompiler/ILAst/ILCodes.cs b/ICSharpCode.Decompiler/ILAst/ILCodes.cs index afb79315c..3dded5bfa 100644 --- a/ICSharpCode.Decompiler/ILAst/ILCodes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILCodes.cs @@ -263,6 +263,8 @@ namespace ICSharpCode.Decompiler.ILAst LoopOrSwitchBreak, LoopContinue, Ldc_Decimal, + YieldBreak, + YieldReturn, Pattern // used for ILAst pattern nodes } @@ -288,6 +290,7 @@ namespace ICSharpCode.Decompiler.ILAst case ILCode.Rethrow: case ILCode.LoopContinue: case ILCode.LoopOrSwitchBreak: + case ILCode.YieldBreak: return false; default: return true; diff --git a/ICSharpCode.Decompiler/ILAst/Pattern.cs b/ICSharpCode.Decompiler/ILAst/Pattern.cs index 72774823f..8fb8c866e 100644 --- a/ICSharpCode.Decompiler/ILAst/Pattern.cs +++ b/ICSharpCode.Decompiler/ILAst/Pattern.cs @@ -45,7 +45,7 @@ namespace ICSharpCode.Decompiler.ILAst public class LoadFromVariable : ILExpression { - IVariablePattern v; + readonly IVariablePattern v; public LoadFromVariable(IVariablePattern v) : base(ILCode.Pattern, null) { @@ -59,6 +59,23 @@ namespace ICSharpCode.Decompiler.ILAst } } + public class LoadFromArgument : ILExpression + { + int index; + public static readonly LoadFromArgument This = new LoadFromArgument(-1); + + public LoadFromArgument(int index) : base(ILCode.Pattern, null) + { + this.index = index; + } + + public override bool Match(ILNode other) + { + ILExpression expr = other as ILExpression; + return expr != null && expr.Code == ILCode.Ldarg && ((ParameterDefinition)expr.Operand).Index == index; + } + } + public class AnyILExpression : ILExpression { public ILExpression LastMatch; diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs new file mode 100644 index 000000000..28da4308e --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -0,0 +1,942 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.ILAst +{ + public class YieldReturnDecompiler + { + // For a description on the code generated by the C# compiler for yield return: + // http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx + + // The idea here is: + // - Figure out whether the current method is instanciating an enumerator + // - Figure out which of the fields is the state field + // - Construct an exception table based on states. This allows us to determine, for each state, what the parent try block is. + + /// + /// This exception is thrown when we find something else than we expect from the C# compiler. + /// This aborts the analysis and makes the whole transform fail. + /// + class YieldAnalysisFailedException : Exception {} + + DecompilerContext context; + TypeDefinition enumeratorType; + MethodDefinition enumeratorCtor; + MethodDefinition disposeMethod; + FieldDefinition stateField; + FieldDefinition currentField; + Dictionary fieldToParameterMap = new Dictionary(); + List newBody; + + #region Run() method + public static void Run(DecompilerContext context, ILBlock method) + { + if (!context.Settings.YieldReturn) + return; // abort if enumerator decompilation is disabled + var yrd = new YieldReturnDecompiler(); + yrd.context = context; + if (!yrd.MatchEnumeratorCreationPattern(method)) + return; + yrd.enumeratorType = yrd.enumeratorCtor.DeclaringType; + #if DEBUG + if (Debugger.IsAttached) { + yrd.Run(); + } else { + #endif + try { + yrd.Run(); + } catch (YieldAnalysisFailedException) { + return; + } + #if DEBUG + } + #endif + method.Body.Clear(); + method.EntryGoto = null; + method.Body.AddRange(yrd.newBody); + } + + void Run() + { + AnalyzeCtor(); + AnalyzeCurrentProperty(); + ResolveIEnumerableIEnumeratorFieldMapping(); + ConstructExceptionTable(); + AnalyzeMoveNext(); + TranslateFieldsToLocalAccess(); + } + #endregion + + #region Match the enumerator creation pattern + bool MatchEnumeratorCreationPattern(ILBlock method) + { + if (method.Body.Count == 0) + return false; + ILExpression newObj; + if (method.Body.Count == 1) { + // ret(newobj(...)) + if (method.Body[0].Match(ILCode.Ret, out newObj)) + return MatchEnumeratorCreationNewObj(newObj, out enumeratorCtor); + else + return false; + } + // stloc(var_1, newobj(..) + ILVariable var1; + if (!method.Body[0].Match(ILCode.Stloc, out var1, out newObj)) + return false; + if (!MatchEnumeratorCreationNewObj(newObj, out enumeratorCtor)) + return false; + + int i; + for (i = 1; i < method.Body.Count; i++) { + // stfld(..., ldloc(var_1), ldarg(...)) + FieldReference storedField; + ILExpression ldloc, ldarg; + if (!method.Body[i].Match(ILCode.Stfld, out storedField, out ldloc, out ldarg)) + break; + if (ldloc.Code != ILCode.Ldloc || ldarg.Code != ILCode.Ldarg) + return false; + storedField = GetFieldDefinition(storedField); + if (ldloc.Operand != var1 || storedField == null) + return false; + fieldToParameterMap[(FieldDefinition)storedField] = (ParameterDefinition)ldarg.Operand; + } + ILVariable var2; + ILExpression ldlocForStloc2; + if (i < method.Body.Count && method.Body[i].Match(ILCode.Stloc, out var2, out ldlocForStloc2)) { + // stloc(var_2, ldloc(var_1)) + if (ldlocForStloc2.Code != ILCode.Ldloc || ldlocForStloc2.Operand != var1) + return false; + i++; + } else { + // the compiler might skip the above instruction in release builds; in that case, it directly returns stloc.Operand + var2 = var1; + } + if (!SkipDummyBr(method, ref i)) + return false; + ILExpression retArg; + if (i < method.Body.Count && method.Body[i].Match(ILCode.Ret, out retArg)) { + // ret(ldloc(var_2)) + if (retArg.Code == ILCode.Ldloc && retArg.Operand == var2) { + return true; + } + } + return false; + } + + static FieldDefinition GetFieldDefinition(FieldReference field) + { + if (field != null && field.DeclaringType.IsGenericInstance) + return field.Resolve(); + else + return field as FieldDefinition; + } + + static MethodDefinition GetMethodDefinition(MethodReference method) + { + if (method != null && method.DeclaringType.IsGenericInstance) + return method.Resolve(); + else + return method as MethodDefinition; + } + + bool SkipDummyBr(ILBlock method, ref int i) + { + ILLabel target; + if (i + 1 < method.Body.Count && method.Body[i].Match(ILCode.Br, out target)) { + if (target != method.Body[i + 1]) + return false; + i += 2; + } + return true; + } + + bool MatchEnumeratorCreationNewObj(ILExpression expr, out MethodDefinition ctor) + { + // newobj(CurrentType/...::.ctor, ldc.i4(-2)) + ctor = null; + if (expr.Code != ILCode.Newobj || expr.Arguments.Count != 1) + return false; + if (expr.Arguments[0].Code != ILCode.Ldc_I4) + return false; + int initialState = (int)expr.Arguments[0].Operand; + if (!(initialState == -2 || initialState == 0)) + return false; + ctor = GetMethodDefinition(expr.Operand as MethodReference); + if (ctor == null || ctor.DeclaringType.DeclaringType != context.CurrentType) + return false; + return IsCompilerGeneratorEnumerator(ctor.DeclaringType); + } + + public static bool IsCompilerGeneratorEnumerator(TypeDefinition type) + { + if (!(type.Name.StartsWith("<", StringComparison.Ordinal) && type.IsCompilerGenerated())) + return false; + foreach (TypeReference i in type.Interfaces) { + if (i.Namespace == "System.Collections" && i.Name == "IEnumerator") + return true; + } + return false; + } + #endregion + + #region Figure out what the 'state' field is (analysis of .ctor()) + /// + /// Looks at the enumerator's ctor and figures out which of the fields holds the state. + /// + void AnalyzeCtor() + { + ILBlock method = CreateILAst(enumeratorCtor); + + ILExpression stfldPattern = new ILExpression(ILCode.Stfld, ILExpression.AnyOperand, LoadFromArgument.This, new LoadFromArgument(0)); + + foreach (ILNode node in method.Body) { + if (stfldPattern.Match(node)) { + stateField = GetFieldDefinition(((ILExpression)node).Operand as FieldReference); + } + } + if (stateField == null) + throw new YieldAnalysisFailedException(); + } + + /// + /// Creates ILAst for the specified method, optimized up to before the 'YieldReturn' step. + /// + ILBlock CreateILAst(MethodDefinition method) + { + if (method == null || !method.HasBody) + throw new YieldAnalysisFailedException(); + + ILBlock ilMethod = new ILBlock(); + ILAstBuilder astBuilder = new ILAstBuilder(); + ilMethod.Body = astBuilder.Build(method, true); + ILAstOptimizer optimizer = new ILAstOptimizer(); + optimizer.Optimize(context, ilMethod, ILAstOptimizationStep.YieldReturn); + return ilMethod; + } + #endregion + + #region Figure out what the 'current' field is (analysis of get_Current()) + static readonly ILExpression returnFieldFromThisPattern = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromArgument.This)); + + /// + /// Looks at the enumerator's get_Current method and figures out which of the fields holds the current value. + /// + void AnalyzeCurrentProperty() + { + MethodDefinition getCurrentMethod = enumeratorType.Methods.FirstOrDefault( + m => m.Name.StartsWith("System.Collections.Generic.IEnumerator", StringComparison.Ordinal) + && m.Name.EndsWith(".get_Current", StringComparison.Ordinal)); + ILBlock method = CreateILAst(getCurrentMethod); + if (method.Body.Count == 1) { + // release builds directly return the current field + if (returnFieldFromThisPattern.Match(method.Body[0])) { + currentField = GetFieldDefinition(((ILExpression)method.Body[0]).Arguments[0].Operand as FieldReference); + } + } else { + StoreToVariable v = new StoreToVariable(new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromArgument.This)); + if (v.Match(method.Body[0])) { + int i = 1; + if (SkipDummyBr(method, ref i) && i == method.Body.Count - 1) { + if (new ILExpression(ILCode.Ret, null, new LoadFromVariable(v)).Match(method.Body[i])) { + currentField = GetFieldDefinition(((ILExpression)method.Body[0]).Arguments[0].Operand as FieldReference); + } + } + } + } + if (currentField == null) + throw new YieldAnalysisFailedException(); + } + #endregion + + #region Figure out the mapping of IEnumerable fields to IEnumerator fields (analysis of GetEnumerator()) + void ResolveIEnumerableIEnumeratorFieldMapping() + { + MethodDefinition getEnumeratorMethod = enumeratorType.Methods.FirstOrDefault( + m => m.Name.StartsWith("System.Collections.Generic.IEnumerable", StringComparison.Ordinal) + && m.Name.EndsWith(".GetEnumerator", StringComparison.Ordinal)); + if (getEnumeratorMethod == null) + return; // no mappings (maybe it's just an IEnumerator implementation?) + + ILExpression mappingPattern = new ILExpression( + ILCode.Stfld, ILExpression.AnyOperand, new AnyILExpression(), + new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromArgument.This)); + + ILBlock method = CreateILAst(getEnumeratorMethod); + foreach (ILNode node in method.Body) { + if (mappingPattern.Match(node)) { + ILExpression stfld = (ILExpression)node; + FieldDefinition storedField = GetFieldDefinition(stfld.Operand as FieldReference); + FieldDefinition loadedField = GetFieldDefinition(stfld.Arguments[1].Operand as FieldReference); + if (storedField != null && loadedField != null) { + ParameterDefinition mappedParameter; + if (fieldToParameterMap.TryGetValue(loadedField, out mappedParameter)) + fieldToParameterMap[storedField] = mappedParameter; + } + } + } + } + #endregion + + #region Construction of the exception table (analysis of Dispose()) + // We construct the exception table by analyzing the enumerator's Dispose() method. + + // Assumption: there are no loops/backward jumps + // We 'run' the code, with "state" being a symbolic variable + // so it can form expressions like "state + x" (when there's a sub instruction) + // For each instruction, we maintain a list of value ranges for state for which the instruction is reachable. + // This is (int.MinValue, int.MaxValue) for the first instruction. + // These ranges are propagated depending on the conditional jumps performed by the code. + + Dictionary finallyMethodToStateInterval; + + void ConstructExceptionTable() + { + disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose"); + ILBlock ilMethod = CreateILAst(disposeMethod); + + finallyMethodToStateInterval = new Dictionary(); + + InitStateRanges(ilMethod.Body[0]); + AssignStateRanges(ilMethod.Body, ilMethod.Body.Count, forDispose: true); + + // Now look at the finally blocks: + foreach (var tryFinally in ilMethod.GetSelfAndChildrenRecursive()) { + Interval interval = ranges[tryFinally.TryBlock.Body[0]].ToEnclosingInterval(); + var finallyBody = tryFinally.FinallyBlock.Body; + if (!(finallyBody.Count == 2 || finallyBody.Count == 3)) + throw new YieldAnalysisFailedException(); + ILExpression call = finallyBody[0] as ILExpression; + if (call == null || call.Code != ILCode.Call || call.Arguments.Count != 1) + throw new YieldAnalysisFailedException(); + if (call.Arguments[0].Code != ILCode.Ldarg || ((ParameterDefinition)call.Arguments[0].Operand).Index >= 0) + throw new YieldAnalysisFailedException(); + if (finallyBody.Count == 3 && !finallyBody[1].Match(ILCode.Nop)) + throw new YieldAnalysisFailedException(); + if (!finallyBody[finallyBody.Count - 1].Match(ILCode.Endfinally)) + throw new YieldAnalysisFailedException(); + + MethodDefinition mdef = GetMethodDefinition(call.Operand as MethodReference); + if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) + throw new YieldAnalysisFailedException(); + finallyMethodToStateInterval.Add(mdef, interval); + } + ranges = null; + } + #endregion + + #region Assign StateRanges / Symbolic Execution (used for analysis of Dispose() and MoveNext()) + #region struct Interval / class StateRange + struct Interval + { + public readonly int Start, End; + + public Interval(int start, int end) + { + Debug.Assert(start <= end || (start == 0 && end == -1)); + this.Start = start; + this.End = end; + } + + public override string ToString() + { + return string.Format("({0} to {1})", Start, End); + } + } + + class StateRange + { + readonly List data = new List(); + + public StateRange() + { + } + + public StateRange(int start, int end) + { + this.data.Add(new Interval(start, end)); + } + + public bool Contains(int val) + { + foreach (Interval v in data) { + if (v.Start <= val && val <= v.End) + return true; + } + return false; + } + + public void UnionWith(StateRange other) + { + data.AddRange(other.data); + } + + /// + /// Unions this state range with (other intersect (minVal to maxVal)) + /// + public void UnionWith(StateRange other, int minVal, int maxVal) + { + foreach (Interval v in other.data) { + int start = Math.Max(v.Start, minVal); + int end = Math.Min(v.End, maxVal); + if (start <= end) + data.Add(new Interval(start, end)); + } + } + + /// + /// Merges overlapping interval ranges. + /// + public void Simplify() + { + if (data.Count < 2) + return; + data.Sort((a, b) => a.Start.CompareTo(b.Start)); + Interval prev = data[0]; + int prevIndex = 0; + for (int i = 1; i < data.Count; i++) { + Interval next = data[i]; + Debug.Assert(prev.Start <= next.Start); + if (next.Start <= prev.End + 1) { // intervals overlapping or touching + prev = new Interval(prev.Start, Math.Max(prev.End, next.End)); + data[prevIndex] = prev; + data[i] = new Interval(0, -1); // mark as deleted + } else { + prev = next; + prevIndex = i; + } + } + data.RemoveAll(i => i.Start > i.End); // remove all entries that were marked as deleted + } + + public override string ToString() + { + return string.Join(",", data); + } + + public Interval ToEnclosingInterval() + { + if (data.Count == 0) + throw new YieldAnalysisFailedException(); + return new Interval(data[0].Start, data[data.Count - 1].End); + } + } + #endregion + + DefaultDictionary ranges; + ILVariable rangeAnalysisStateVariable; + + /// + /// Initializes the state range logic: + /// Clears 'ranges' and sets 'ranges[entryPoint]' to the full range (int.MinValue to int.MaxValue) + /// + void InitStateRanges(ILNode entryPoint) + { + ranges = new DefaultDictionary(n => new StateRange()); + ranges[entryPoint] = new StateRange(int.MinValue, int.MaxValue); + rangeAnalysisStateVariable = null; + } + + int AssignStateRanges(List body, int bodyLength, bool forDispose) + { + if (bodyLength == 0) + return 0; + for (int i = 0; i < bodyLength; i++) { + StateRange nodeRange = ranges[body[i]]; + nodeRange.Simplify(); + + ILLabel label = body[i] as ILLabel; + if (label != null) { + ranges[body[i + 1]].UnionWith(nodeRange); + continue; + } + + ILTryCatchBlock tryFinally = body[i] as ILTryCatchBlock; + if (tryFinally != null) { + if (!forDispose || tryFinally.CatchBlocks.Count != 0 || tryFinally.FaultBlock != null || tryFinally.FinallyBlock == null) + throw new YieldAnalysisFailedException(); + ranges[tryFinally.TryBlock].UnionWith(nodeRange); + AssignStateRanges(tryFinally.TryBlock.Body, tryFinally.TryBlock.Body.Count, forDispose); + continue; + } + + ILExpression expr = body[i] as ILExpression; + if (expr == null) + throw new YieldAnalysisFailedException(); + switch (expr.Code) { + case ILCode.Switch: + { + SymbolicValue val = Eval(expr.Arguments[0]); + if (val.Type != SymbolicValueType.State) + throw new YieldAnalysisFailedException(); + ILLabel[] targetLabels = (ILLabel[])expr.Operand; + for (int j = 0; j < targetLabels.Length; j++) { + int state = j - val.Constant; + ranges[targetLabels[j]].UnionWith(nodeRange, state, state); + } + StateRange nextRange = ranges[body[i + 1]]; + nextRange.UnionWith(nodeRange, int.MinValue, -1 - val.Constant); + nextRange.UnionWith(nodeRange, targetLabels.Length - val.Constant, int.MaxValue); + break; + } + case ILCode.Br: + case ILCode.Leave: + ranges[(ILLabel)expr.Operand].UnionWith(nodeRange); + break; + case ILCode.Brtrue: + { + SymbolicValue val = Eval(expr.Arguments[0]); + if (val.Type == SymbolicValueType.StateEquals) { + ranges[(ILLabel)expr.Operand].UnionWith(nodeRange, val.Constant, val.Constant); + StateRange nextRange = ranges[body[i + 1]]; + nextRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); + nextRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); + } else if (val.Type == SymbolicValueType.StateInEquals) { + ranges[body[i + 1]].UnionWith(nodeRange, val.Constant, val.Constant); + StateRange targetRange = ranges[(ILLabel)expr.Operand]; + targetRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); + targetRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); + } else { + throw new YieldAnalysisFailedException(); + } + break; + } + case ILCode.Nop: + ranges[body[i + 1]].UnionWith(nodeRange); + break; + case ILCode.Ret: + break; + case ILCode.Stloc: + { + SymbolicValue val = Eval(expr.Arguments[0]); + if (val.Type == SymbolicValueType.State && val.Constant == 0 && rangeAnalysisStateVariable == null) + rangeAnalysisStateVariable = (ILVariable)expr.Operand; + else + throw new YieldAnalysisFailedException(); + goto case ILCode.Nop; + } + case ILCode.Call: + // in some cases (e.g. foreach over array) the C# compiler produces a finally method outside of try-finally blocks + if (forDispose) { + MethodDefinition mdef = GetMethodDefinition(expr.Operand as MethodReference); + if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) + throw new YieldAnalysisFailedException(); + finallyMethodToStateInterval.Add(mdef, nodeRange.ToEnclosingInterval()); + } else { + throw new YieldAnalysisFailedException(); + } + break; + default: + if (forDispose) + throw new YieldAnalysisFailedException(); + else + return i; + } + } + return bodyLength; + } + + enum SymbolicValueType + { + /// + /// int: Constant (result of ldc.i4) + /// + IntegerConstant, + /// + /// int: State + Constant + /// + State, + /// + /// This pointer (result of ldarg.0) + /// + This, + /// + /// bool: State == Constant + /// + StateEquals, + /// + /// bool: State != Constant + /// + StateInEquals + } + + struct SymbolicValue + { + public readonly int Constant; + public readonly SymbolicValueType Type; + + public SymbolicValue(SymbolicValueType type, int constant = 0) + { + this.Type = type; + this.Constant = constant; + } + + public override string ToString() + { + return string.Format("[SymbolicValue {0}: {1}]", this.Type, this.Constant); + } + } + + SymbolicValue Eval(ILExpression expr) + { + SymbolicValue left, right; + switch (expr.Code) { + case ILCode.Sub: + left = Eval(expr.Arguments[0]); + right = Eval(expr.Arguments[1]); + if (left.Type != SymbolicValueType.State && left.Type != SymbolicValueType.IntegerConstant) + throw new YieldAnalysisFailedException(); + if (right.Type != SymbolicValueType.IntegerConstant) + throw new YieldAnalysisFailedException(); + return new SymbolicValue(left.Type, unchecked ( left.Constant - right.Constant )); + case ILCode.Ldfld: + if (Eval(expr.Arguments[0]).Type != SymbolicValueType.This) + throw new YieldAnalysisFailedException(); + if (GetFieldDefinition(expr.Operand as FieldReference) != stateField) + throw new YieldAnalysisFailedException(); + return new SymbolicValue(SymbolicValueType.State); + case ILCode.Ldloc: + if (expr.Operand == rangeAnalysisStateVariable) + return new SymbolicValue(SymbolicValueType.State); + else + throw new YieldAnalysisFailedException(); + case ILCode.Ldarg: + if (((ParameterDefinition)expr.Operand).Index < 0) + return new SymbolicValue(SymbolicValueType.This); + else + throw new YieldAnalysisFailedException(); + case ILCode.Ldc_I4: + return new SymbolicValue(SymbolicValueType.IntegerConstant, (int)expr.Operand); + case ILCode.Ceq: + left = Eval(expr.Arguments[0]); + right = Eval(expr.Arguments[1]); + if (left.Type != SymbolicValueType.State || right.Type != SymbolicValueType.IntegerConstant) + throw new YieldAnalysisFailedException(); + // bool: (state + left.Constant == right.Constant) + // bool: (state == right.Constant - left.Constant) + return new SymbolicValue(SymbolicValueType.StateEquals, unchecked ( right.Constant - left.Constant )); + case ILCode.LogicNot: + SymbolicValue val = Eval(expr.Arguments[0]); + if (val.Type == SymbolicValueType.StateEquals) + return new SymbolicValue(SymbolicValueType.StateInEquals, val.Constant); + else if (val.Type == SymbolicValueType.StateInEquals) + return new SymbolicValue(SymbolicValueType.StateEquals, val.Constant); + else + throw new YieldAnalysisFailedException(); + default: + throw new YieldAnalysisFailedException(); + } + } + #endregion + + #region Analysis of MoveNext() + ILVariable returnVariable; + ILLabel returnLabel; + ILLabel returnFalseLabel; + + void AnalyzeMoveNext() + { + MethodDefinition moveNextMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "MoveNext"); + ILBlock ilMethod = CreateILAst(moveNextMethod); + + if (ilMethod.Body.Count == 0) + throw new YieldAnalysisFailedException(); + ILExpression lastReturnArg; + if (!ilMethod.Body.Last().Match(ILCode.Ret, out lastReturnArg)) + throw new YieldAnalysisFailedException(); + + ilMethod.Body.RemoveAll(n => n.Match(ILCode.Nop)); // remove nops + + // There are two possibilities: + if (lastReturnArg.Code == ILCode.Ldloc) { + // a) the compiler uses a variable for returns (in debug builds, or when there are try-finally blocks) + returnVariable = (ILVariable)lastReturnArg.Operand; + returnLabel = ilMethod.Body.ElementAtOrDefault(ilMethod.Body.Count - 2) as ILLabel; + if (returnLabel == null) + throw new YieldAnalysisFailedException(); + } else { + // b) the compiler directly returns constants + returnVariable = null; + returnLabel = null; + // In this case, the last return must return false. + if (lastReturnArg.Code != ILCode.Ldc_I4 || (int)lastReturnArg.Operand != 0) + throw new YieldAnalysisFailedException(); + } + + ILTryCatchBlock tryFaultBlock = ilMethod.Body[0] as ILTryCatchBlock; + List body; + int bodyLength; + if (tryFaultBlock != null) { + // there are try-finally blocks + if (returnVariable == null) // in this case, we must use a return variable + throw new YieldAnalysisFailedException(); + // must be a try-fault block: + if (tryFaultBlock.CatchBlocks.Count != 0 || tryFaultBlock.FinallyBlock != null || tryFaultBlock.FaultBlock == null) + throw new YieldAnalysisFailedException(); + + ILBlock faultBlock = tryFaultBlock.FaultBlock; + // Ensure the fault block contains the call to Dispose(). + if (!(faultBlock.Body.Count == 2 || faultBlock.Body.Count == 3)) + throw new YieldAnalysisFailedException(); + MethodReference disposeMethodRef; + ILExpression disposeArg; + if (!faultBlock.Body[0].Match(ILCode.Call, out disposeMethodRef, out disposeArg)) + throw new YieldAnalysisFailedException(); + if (GetMethodDefinition(disposeMethodRef) != disposeMethod || !LoadFromArgument.This.Match(disposeArg)) + throw new YieldAnalysisFailedException(); + if (faultBlock.Body.Count == 3 && !faultBlock.Body[1].Match(ILCode.Nop)) + throw new YieldAnalysisFailedException(); + if (!faultBlock.Body[faultBlock.Body.Count - 1].Match(ILCode.Endfinally)) + throw new YieldAnalysisFailedException(); + + body = tryFaultBlock.TryBlock.Body; + body.RemoveAll(n => n.Match(ILCode.Nop)); // remove nops + bodyLength = body.Count; + } else { + // no try-finally blocks + body = ilMethod.Body; + if (returnVariable == null) + bodyLength = body.Count - 1; // all except for the return statement + else + bodyLength = body.Count - 2; // all except for the return label and statement + } + + // Now verify that the last instruction in the body is 'ret(false)' + if (returnVariable != null) { + // If we don't have a return variable, we already verified that above. + if (bodyLength < 2) + throw new YieldAnalysisFailedException(); + ILExpression leave = body[bodyLength - 1] as ILExpression; + if (leave == null || leave.Operand != returnLabel || !(leave.Code == ILCode.Br || leave.Code == ILCode.Leave)) + throw new YieldAnalysisFailedException(); + ILExpression store0 = body[bodyLength - 2] as ILExpression; + if (store0 == null || store0.Code != ILCode.Stloc || store0.Operand != returnVariable) + throw new YieldAnalysisFailedException(); + if (store0.Arguments[0].Code != ILCode.Ldc_I4 || (int)store0.Arguments[0].Operand != 0) + throw new YieldAnalysisFailedException(); + + bodyLength -= 2; // don't conside the 'ret(false)' part of the body + } + // verify that the last element in the body is a label pointing to the 'ret(false)' + returnFalseLabel = body.ElementAtOrDefault(bodyLength - 1) as ILLabel; + if (returnFalseLabel == null) + throw new YieldAnalysisFailedException(); + + InitStateRanges(body[0]); + int pos = AssignStateRanges(body, bodyLength, forDispose: false); + if (pos > 0 && body[pos - 1] is ILLabel) { + pos--; + } else { + // ensure that the first element at body[pos] is a label: + ILLabel newLabel = new ILLabel(); + newLabel.Name = "YieldReturnEntryPoint"; + ranges[newLabel] = ranges[body[pos]]; // give the label the range of the instruction at body[pos] + + body.Insert(pos, newLabel); + bodyLength++; + } + + List> labels = new List>(); + for (int i = pos; i < bodyLength; i++) { + ILLabel label = body[i] as ILLabel; + if (label != null) { + labels.Add(new KeyValuePair(label, ranges[label])); + } + } + + ConvertBody(body, pos, bodyLength, labels); + } + #endregion + + #region ConvertBody + struct SetState + { + public readonly int NewBodyPos; + public readonly int NewState; + + public SetState(int newBodyPos, int newState) + { + this.NewBodyPos = newBodyPos; + this.NewState = newState; + } + } + + void ConvertBody(List body, int startPos, int bodyLength, List> labels) + { + newBody = new List(); + newBody.Add(MakeGoTo(labels, 0)); + List stateChanges = new List(); + int currentState = -1; + // Copy all instructions from the old body to newBody. + for (int pos = startPos; pos < bodyLength; pos++) { + ILExpression expr = body[pos] as ILExpression; + if (expr != null && expr.Code == ILCode.Stfld && LoadFromArgument.This.Match(expr.Arguments[0])) { + // Handle stores to 'state' or 'current' + if (GetFieldDefinition(expr.Operand as FieldReference) == stateField) { + if (expr.Arguments[1].Code != ILCode.Ldc_I4) + throw new YieldAnalysisFailedException(); + currentState = (int)expr.Arguments[1].Operand; + stateChanges.Add(new SetState(newBody.Count, currentState)); + } else if (GetFieldDefinition(expr.Operand as FieldReference) == currentField) { + newBody.Add(new ILExpression(ILCode.YieldReturn, null, expr.Arguments[1])); + } else { + newBody.Add(body[pos]); + } + } else if (returnVariable != null && expr != null && expr.Code == ILCode.Stloc && expr.Operand == returnVariable) { + // handle store+branch to the returnVariable + ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression; + if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnLabel || expr.Arguments[0].Code != ILCode.Ldc_I4) + throw new YieldAnalysisFailedException(); + int val = (int)expr.Arguments[0].Operand; + if (val == 0) { + newBody.Add(MakeGoTo(returnFalseLabel)); + } else if (val == 1) { + newBody.Add(MakeGoTo(labels, currentState)); + } else { + throw new YieldAnalysisFailedException(); + } + } else if (expr != null && expr.Code == ILCode.Ret) { + if (expr.Arguments.Count != 1 || expr.Arguments[0].Code != ILCode.Ldc_I4) + throw new YieldAnalysisFailedException(); + // handle direct return (e.g. in release builds) + int val = (int)expr.Arguments[0].Operand; + if (val == 0) { + newBody.Add(MakeGoTo(returnFalseLabel)); + } else if (val == 1) { + newBody.Add(MakeGoTo(labels, currentState)); + } else { + throw new YieldAnalysisFailedException(); + } + } else if (expr != null && expr.Code == ILCode.Call && expr.Arguments.Count == 1 && LoadFromArgument.This.Match(expr.Arguments[0])) { + MethodDefinition method = GetMethodDefinition(expr.Operand as MethodReference); + if (method == null) + throw new YieldAnalysisFailedException(); + Interval interval; + if (method == disposeMethod) { + // Explicit call to dispose is used for "yield break;" within the method. + ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression; + if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnFalseLabel) + throw new YieldAnalysisFailedException(); + newBody.Add(MakeGoTo(returnFalseLabel)); + } else if (finallyMethodToStateInterval.TryGetValue(method, out interval)) { + // Call to Finally-method + int index = stateChanges.FindIndex(ss => ss.NewState >= interval.Start && ss.NewState <= interval.End); + if (index < 0) + throw new YieldAnalysisFailedException(); + + ILLabel label = new ILLabel(); + label.Name = "JumpOutOfTryFinally" + interval.Start + "_" + interval.End; + newBody.Add(new ILExpression(ILCode.Leave, label)); + + SetState stateChange = stateChanges[index]; + // Move all instructions from stateChange.Pos to newBody.Count into a try-block + stateChanges.RemoveRange(index, stateChanges.Count - index); // remove all state changes up to the one we found + ILTryCatchBlock tryFinally = new ILTryCatchBlock(); + tryFinally.TryBlock = new ILBlock(newBody.GetRange(stateChange.NewBodyPos, newBody.Count - stateChange.NewBodyPos)); + newBody.RemoveRange(stateChange.NewBodyPos, newBody.Count - stateChange.NewBodyPos); // remove all nodes that we just moved into the try block + tryFinally.CatchBlocks = new List(); + tryFinally.FinallyBlock = ConvertFinallyBlock(method); + newBody.Add(tryFinally); + newBody.Add(label); + } + } else { + newBody.Add(body[pos]); + } + } + newBody.Add(new ILExpression(ILCode.YieldBreak, null)); + } + + ILExpression MakeGoTo(ILLabel targetLabel) + { + if (targetLabel == returnFalseLabel) + return new ILExpression(ILCode.YieldBreak, null); + else + return new ILExpression(ILCode.Br, targetLabel); + } + + ILExpression MakeGoTo(List> labels, int state) + { + foreach (var pair in labels) { + if (pair.Value.Contains(state)) + return MakeGoTo(pair.Key); + } + throw new YieldAnalysisFailedException(); + } + + ILBlock ConvertFinallyBlock(MethodDefinition finallyMethod) + { + ILBlock block = CreateILAst(finallyMethod); + block.Body.RemoveAll(n => n.Match(ILCode.Nop)); + // Get rid of assignment to state + FieldReference stfld; + List args; + if (block.Body.Count > 0 && block.Body[0].Match(ILCode.Stfld, out stfld, out args)) { + if (GetFieldDefinition(stfld) == stateField && LoadFromArgument.This.Match(args[0])) + block.Body.RemoveAt(0); + } + // Convert ret to endfinally + foreach (ILExpression expr in block.GetSelfAndChildrenRecursive()) { + if (expr.Code == ILCode.Ret) + expr.Code = ILCode.Endfinally; + } + return block; + } + #endregion + + #region TranslateFieldsToLocalAccess + void TranslateFieldsToLocalAccess() + { + var fieldToLocalMap = new DefaultDictionary(f => new ILVariable { Name = f.Name, Type = f.FieldType }); + foreach (ILNode node in newBody) { + foreach (ILExpression expr in node.GetSelfAndChildrenRecursive()) { + FieldDefinition field = GetFieldDefinition(expr.Operand as FieldReference); + if (field != null) { + switch (expr.Code) { + case ILCode.Ldfld: + if (LoadFromArgument.This.Match(expr.Arguments[0])) { + if (fieldToParameterMap.ContainsKey(field)) { + expr.Code = ILCode.Ldarg; + expr.Operand = fieldToParameterMap[field]; + } else { + expr.Code = ILCode.Ldloc; + expr.Operand = fieldToLocalMap[field]; + } + expr.Arguments.Clear(); + } + break; + case ILCode.Stfld: + if (LoadFromArgument.This.Match(expr.Arguments[0])) { + if (fieldToParameterMap.ContainsKey(field)) { + expr.Code = ILCode.Starg; + expr.Operand = fieldToParameterMap[field]; + } else { + expr.Code = ILCode.Stloc; + expr.Operand = fieldToLocalMap[field]; + } + expr.Arguments.RemoveAt(0); + } + break; + case ILCode.Ldflda: + if (fieldToParameterMap.ContainsKey(field)) { + expr.Code = ILCode.Ldarga; + expr.Operand = fieldToParameterMap[field]; + } else { + expr.Code = ILCode.Ldloca; + expr.Operand = fieldToLocalMap[field]; + } + expr.Arguments.Clear(); + break; + } + } + } + } + } + #endregion + } +} diff --git a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj index a24ef753b..42ee0a0cd 100644 --- a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj @@ -53,6 +53,7 @@ + diff --git a/ICSharpCode.Decompiler/Tests/YieldReturn.cs b/ICSharpCode.Decompiler/Tests/YieldReturn.cs new file mode 100644 index 000000000..de9bd1efb --- /dev/null +++ b/ICSharpCode.Decompiler/Tests/YieldReturn.cs @@ -0,0 +1,103 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; + +public static class YieldReturn +{ + public static IEnumerable SimpleYieldReturn() + { + yield return "A"; + yield return "B"; + yield return "C"; + } + + public static IEnumerable YieldReturnInLoop() + { + for (int i = 0; i < 100; i++) { + yield return i; + } + } + + public static IEnumerable YieldReturnWithTryFinally() + { + yield return 0; + try { + yield return 1; + } finally { + Console.WriteLine("Finally!"); + } + yield return 2; + } + + + public static IEnumerable YieldReturnWithNestedTryFinally(bool breakInMiddle) + { + Console.WriteLine("Start of method - 1"); + yield return "Start of method"; + Console.WriteLine("Start of method - 2"); + try { + Console.WriteLine("Within outer try - 1"); + yield return "Within outer try"; + Console.WriteLine("Within outer try - 2"); + try { + Console.WriteLine("Within inner try - 1"); + yield return "Within inner try"; + Console.WriteLine("Within inner try - 2"); + if (breakInMiddle) + yield break; + Console.WriteLine("End of inner try - 1"); + yield return "End of inner try"; + Console.WriteLine("End of inner try - 2"); + } finally { + Console.WriteLine("Inner Finally"); + } + Console.WriteLine("End of outer try - 1"); + yield return "End of outer try"; + Console.WriteLine("End of outer try - 2"); + } finally { + Console.WriteLine("Outer Finally"); + } + Console.WriteLine("End of method - 1"); + yield return "End of method"; + Console.WriteLine("End of method - 2"); + } + + public static IEnumerable YieldReturnWithTwoNonNestedFinallyBlocks(IEnumerable input) + { + // outer try-finally block + foreach (string line in input) { + // nested try-finally block + try { + yield return line; + } finally { + Console.WriteLine("Processed " + line); + } + } + yield return "A"; + yield return "B"; + yield return "C"; + yield return "D"; + yield return "E"; + yield return "F"; + // outer try-finally block + foreach (string line in input) + yield return line.ToUpper(); + } + + public static IEnumerable> YieldReturnWithAnonymousMethods1(IEnumerable input) + { + foreach (string line in input) { + yield return () => line; + } + } + + public static IEnumerable> YieldReturnWithAnonymousMethods2(IEnumerable input) + { + foreach (string line in input) { + string copy = line; + yield return () => copy; + } + } +} diff --git a/ILSpy/CSharpLanguage.cs b/ILSpy/CSharpLanguage.cs index c01caaf1c..5c24eadb6 100644 --- a/ILSpy/CSharpLanguage.cs +++ b/ILSpy/CSharpLanguage.cs @@ -376,7 +376,8 @@ namespace ICSharpCode.ILSpy return new AstBuilder( new DecompilerContext { CancellationToken = options.CancellationToken, - CurrentType = currentType + CurrentType = currentType, + Settings = options.DecompilerSettings }); } @@ -419,7 +420,7 @@ namespace ICSharpCode.ILSpy public override bool ShowMember(MemberReference member) { - return showAllMembers || !AstBuilder.MemberIsHidden(member); + return showAllMembers || !AstBuilder.MemberIsHidden(member, new DecompilationOptions().DecompilerSettings); } } } diff --git a/ILSpy/DecompilationOptions.cs b/ILSpy/DecompilationOptions.cs index 8dc953c6d..162fbe29c 100644 --- a/ILSpy/DecompilationOptions.cs +++ b/ILSpy/DecompilationOptions.cs @@ -18,6 +18,7 @@ using System; using System.Threading; +using ICSharpCode.Decompiler; namespace ICSharpCode.ILSpy { @@ -45,5 +46,15 @@ namespace ICSharpCode.ILSpy /// to allow for cooperative cancellation of the decompilation task. /// public CancellationToken CancellationToken { get; set; } + + /// + /// Gets the settings for the decompiler. + /// + public DecompilerSettings DecompilerSettings { get; set; } + + public DecompilationOptions() + { + this.DecompilerSettings = DecompilerSettingsPanel.CurrentDecompilerSettings; + } } } diff --git a/ILSpy/DecompilerSettingsPanel.xaml b/ILSpy/DecompilerSettingsPanel.xaml new file mode 100644 index 000000000..00732070c --- /dev/null +++ b/ILSpy/DecompilerSettingsPanel.xaml @@ -0,0 +1,9 @@ + + + Decompile anonymous methods/lambdas + Decompile enumerators (yield return) + + \ No newline at end of file diff --git a/ILSpy/DecompilerSettingsPanel.xaml.cs b/ILSpy/DecompilerSettingsPanel.xaml.cs new file mode 100644 index 000000000..03d45eef8 --- /dev/null +++ b/ILSpy/DecompilerSettingsPanel.xaml.cs @@ -0,0 +1,67 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Xml.Linq; +using ICSharpCode.Decompiler; + +namespace ICSharpCode.ILSpy +{ + /// + /// Interaction logic for DecompilerSettingsPanel.xaml + /// + [ExportOptionPage("Decompiler")] + partial class DecompilerSettingsPanel : UserControl, IOptionPage + { + public DecompilerSettingsPanel() + { + InitializeComponent(); + } + + public void Load(ILSpySettings settings) + { + this.DataContext = LoadDecompilerSettings(settings); + } + + static DecompilerSettings currentDecompilerSettings; + + public static DecompilerSettings CurrentDecompilerSettings { + get { + return currentDecompilerSettings ?? (currentDecompilerSettings = LoadDecompilerSettings(ILSpySettings.Load())); + } + } + + public static DecompilerSettings LoadDecompilerSettings(ILSpySettings settings) + { + XElement e = settings["DecompilerSettings"]; + DecompilerSettings s = new DecompilerSettings(); + s.AnonymousMethods = (bool?)e.Attribute("anonymousMethods") ?? s.AnonymousMethods; + s.YieldReturn = (bool?)e.Attribute("yieldReturn") ?? s.YieldReturn; + return s; + } + + public void Save(XElement root) + { + DecompilerSettings s = (DecompilerSettings)this.DataContext; + XElement section = new XElement("DecompilerSettings"); + section.SetAttributeValue("anonymousMethods", s.AnonymousMethods); + section.SetAttributeValue("yieldReturn", s.YieldReturn); + + XElement existingElement = root.Element("DecompilerSettings"); + if (existingElement != null) + existingElement.ReplaceWith(section); + else + root.Add(section); + + currentDecompilerSettings = null; // invalidate cached settings + } + } +} \ No newline at end of file diff --git a/ILSpy/ILAstLanguage.cs b/ILSpy/ILAstLanguage.cs index 1db27d100..fc6217a48 100644 --- a/ILSpy/ILAstLanguage.cs +++ b/ILSpy/ILAstLanguage.cs @@ -60,8 +60,7 @@ namespace ICSharpCode.ILSpy new ILAstOptimizer().Optimize(context, ilMethod, abortBeforeStep.Value); } - var allVariables = astBuilder.Variables - .Concat(ilMethod.GetSelfAndChildrenRecursive().Select(e => e.Operand as ILVariable).Where(v => v != null)).Distinct(); + var allVariables = ilMethod.GetSelfAndChildrenRecursive().Select(e => e.Operand as ILVariable).Where(v => v != null).Distinct(); foreach (ILVariable v in allVariables) { output.WriteDefinition(v.Name, v); if (v.Type != null) { diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 49befa9fc..f1bbc05ce 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -91,6 +91,10 @@ + + DecompilerSettingsPanel.xaml + Code + @@ -113,6 +117,10 @@ OpenFromGacDialog.xaml Code + + OptionsDialog.xaml + Code + README.txt @@ -171,8 +179,10 @@ SearchBox.cs + + DecompilerTextView.cs diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index d6ed80f39..6106f967f 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -291,15 +291,20 @@ namespace ICSharpCode.ILSpy } void filterSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + RefreshTreeViewFilter(); + if (e.PropertyName == "Language") { + TreeView_SelectionChanged(null, null); + } + } + + public void RefreshTreeViewFilter() { // filterSettings is mutable; but the ILSpyTreeNode filtering assumes that filter settings are immutable. // Thus, the main window will use one mutable instance (for data-binding), and assign a new clone to the ILSpyTreeNodes whenever the main // mutable instance changes. if (assemblyListTreeNode != null) assemblyListTreeNode.FilterSettings = sessionSettings.FilterSettings.Clone(); - if (e.PropertyName == "Language") { - TreeView_SelectionChanged(null, null); - } } internal AssemblyList AssemblyList { @@ -445,6 +450,11 @@ namespace ICSharpCode.ILSpy decompilerTextView.Decompile(this.CurrentLanguage, this.SelectedNodes, new DecompilationOptions()); } + public void RefreshDecompiledView() + { + TreeView_SelectionChanged(null, null); + } + public DecompilerTextView TextView { get { return decompilerTextView; } } diff --git a/ILSpy/OptionsDialog.xaml b/ILSpy/OptionsDialog.xaml new file mode 100644 index 000000000..c5a9b8fe7 --- /dev/null +++ b/ILSpy/OptionsDialog.xaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/ILSpy/OptionsDialog.xaml.cs b/ILSpy/OptionsDialog.xaml.cs new file mode 100644 index 000000000..fa975c82f --- /dev/null +++ b/ILSpy/OptionsDialog.xaml.cs @@ -0,0 +1,95 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Xml.Linq; + +namespace ICSharpCode.ILSpy +{ + /// + /// Interaction logic for OptionsDialog.xaml + /// + public partial class OptionsDialog : Window + { + [ImportMany("OptionPages", typeof(UIElement), RequiredCreationPolicy = CreationPolicy.NonShared)] + Lazy[] optionPages = null; + + public OptionsDialog() + { + InitializeComponent(); + App.CompositionContainer.ComposeParts(this); + ILSpySettings settings = ILSpySettings.Load(); + foreach (var optionPage in optionPages) { + TabItem tabItem = new TabItem(); + tabItem.Header = optionPage.Metadata.Title; + tabItem.Content = optionPage.Value; + tabControl.Items.Add(tabItem); + + IOptionPage page = optionPage.Value as IOptionPage; + if (page != null) + page.Load(settings); + } + } + + void OKButton_Click(object sender, RoutedEventArgs e) + { + ILSpySettings.Update( + delegate (XElement root) { + foreach (var optionPage in optionPages) { + IOptionPage page = optionPage.Value as IOptionPage; + if (page != null) + page.Save(root); + } + }); + this.DialogResult = true; + Close(); + } + } + + public interface IOptionsMetadata + { + string Title { get; } + } + + public interface IOptionPage + { + void Load(ILSpySettings settings); + void Save(XElement root); + } + + [MetadataAttribute] + [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] + public class ExportOptionPageAttribute : ExportAttribute + { + public ExportOptionPageAttribute(string title) + : base("OptionPages", typeof(UIElement)) + { + this.Title = title; + } + + public string Title { get; private set; } + } + + [ExportMainMenuCommand(Menu = "_View", Header = "_Options", MenuCategory = "Options", MenuOrder = 999)] + sealed class ShowOptionsCommand : SimpleCommand + { + public override void Execute(object parameter) + { + OptionsDialog dlg = new OptionsDialog(); + dlg.Owner = MainWindow.Instance; + if (dlg.ShowDialog() == true) { + MainWindow.Instance.RefreshTreeViewFilter(); + MainWindow.Instance.RefreshDecompiledView(); + } + } + } +} \ No newline at end of file