diff --git a/ICSharpCode.Decompiler/Ast/AstBuilder.cs b/ICSharpCode.Decompiler/Ast/AstBuilder.cs index 9b1d35af4..f9a4b6881 100644 --- a/ICSharpCode.Decompiler/Ast/AstBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstBuilder.cs @@ -48,6 +48,11 @@ namespace ICSharpCode.Decompiler.Ast return true; if (settings.YieldReturn && YieldReturnDecompiler.IsCompilerGeneratorEnumerator(type)) return true; + } else if (type != null && type.IsCompilerGenerated()) { + if (type.Name.StartsWith("", StringComparison.Ordinal)) + return true; + if (type.Name.StartsWith("<>", StringComparison.Ordinal) && type.Name.Contains("AnonymousType")) + return true; } FieldDefinition field = member as FieldDefinition; if (field != null && field.IsCompilerGenerated()) { @@ -752,43 +757,55 @@ namespace ICSharpCode.Decompiler.Ast ConvertCustomAttributes(astProp, propDef); return astProp; } - - CustomEventDeclaration CreateEvent(EventDefinition eventDef) + + AttributedNode CreateEvent(EventDefinition eventDef) { - CustomEventDeclaration astEvent = new CustomEventDeclaration(); - ConvertCustomAttributes(astEvent, eventDef); - astEvent.AddAnnotation(eventDef); - astEvent.Name = CleanName(eventDef.Name); - astEvent.ReturnType = ConvertType(eventDef.EventType, eventDef); - if (eventDef.AddMethod == null || !eventDef.AddMethod.HasOverrides) - astEvent.Modifiers = ConvertModifiers(eventDef.AddMethod); - else - astEvent.PrivateImplementationType = ConvertType(eventDef.AddMethod.Overrides.First().DeclaringType); - if (eventDef.AddMethod != null) { - // Create mapping - used in debugger - MethodMapping methodMapping = eventDef.AddMethod.CreateCodeMapping(CSharpCodeMapping.SourceCodeMappings); - - astEvent.AddAccessor = new Accessor { - Body = AstMethodBodyBuilder.CreateMethodBody(eventDef.AddMethod, context) - }.WithAnnotation(eventDef.AddMethod); - ConvertAttributes(astEvent.AddAccessor, eventDef.AddMethod); - - if (methodMapping != null) - astEvent.AddAccessor.AddAnnotation(methodMapping); - } - if (eventDef.RemoveMethod != null) { - // Create mapping - used in debugger - MethodMapping methodMapping = eventDef.RemoveMethod.CreateCodeMapping(CSharpCodeMapping.SourceCodeMappings); - - astEvent.RemoveAccessor = new Accessor { - Body = AstMethodBodyBuilder.CreateMethodBody(eventDef.RemoveMethod, context) - }.WithAnnotation(eventDef.RemoveMethod); - ConvertAttributes(astEvent.RemoveAccessor, eventDef.RemoveMethod); - - if (methodMapping != null) - astEvent.RemoveAccessor.AddAnnotation(methodMapping); + if (eventDef.AddMethod != null && eventDef.AddMethod.IsAbstract) { + // An abstract event cannot be custom + EventDeclaration astEvent = new EventDeclaration(); + ConvertCustomAttributes(astEvent, eventDef); + astEvent.AddAnnotation(eventDef); + astEvent.Variables.Add(new VariableInitializer(CleanName(eventDef.Name))); + astEvent.ReturnType = ConvertType(eventDef.EventType, eventDef); + if (!eventDef.DeclaringType.IsInterface) + astEvent.Modifiers = ConvertModifiers(eventDef.AddMethod); + return astEvent; + } else { + CustomEventDeclaration astEvent = new CustomEventDeclaration(); + ConvertCustomAttributes(astEvent, eventDef); + astEvent.AddAnnotation(eventDef); + astEvent.Name = CleanName(eventDef.Name); + astEvent.ReturnType = ConvertType(eventDef.EventType, eventDef); + if (eventDef.AddMethod == null || !eventDef.AddMethod.HasOverrides) + astEvent.Modifiers = ConvertModifiers(eventDef.AddMethod); + else + astEvent.PrivateImplementationType = ConvertType(eventDef.AddMethod.Overrides.First().DeclaringType); + if (eventDef.AddMethod != null) { + // Create mapping - used in debugger + MethodMapping methodMapping = eventDef.AddMethod.CreateCodeMapping(CSharpCodeMapping.SourceCodeMappings); + + astEvent.AddAccessor = new Accessor { + Body = AstMethodBodyBuilder.CreateMethodBody(eventDef.AddMethod, context) + }.WithAnnotation(eventDef.AddMethod); + ConvertAttributes(astEvent.AddAccessor, eventDef.AddMethod); + + if (methodMapping != null) + astEvent.AddAccessor.AddAnnotation(methodMapping); + } + if (eventDef.RemoveMethod != null) { + // Create mapping - used in debugger + MethodMapping methodMapping = eventDef.RemoveMethod.CreateCodeMapping(CSharpCodeMapping.SourceCodeMappings); + + astEvent.RemoveAccessor = new Accessor { + Body = AstMethodBodyBuilder.CreateMethodBody(eventDef.RemoveMethod, context) + }.WithAnnotation(eventDef.RemoveMethod); + ConvertAttributes(astEvent.RemoveAccessor, eventDef.RemoveMethod); + + if (methodMapping != null) + astEvent.RemoveAccessor.AddAnnotation(methodMapping); + } + return astEvent; } - return astEvent; } FieldDeclaration CreateField(FieldDefinition fieldDef) diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index 8b732fc9b..5d74087c8 100644 --- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -408,9 +408,11 @@ namespace ICSharpCode.Decompiler.Ast case ILCode.Initblk: return InlineAssembly(byteCode, args); case ILCode.Initobj: if (args[0] is DirectionExpression) - return new AssignmentExpression(((DirectionExpression)args[0]).Expression.Detach(), new DefaultValueExpression { Type = operandAsTypeRef }); + return new AssignmentExpression(((DirectionExpression)args[0]).Expression.Detach(), MakeDefaultValue((TypeReference)operand)); else return InlineAssembly(byteCode, args); + case ILCode.DefaultValue: + return MakeDefaultValue((TypeReference)operand); case ILCode.Jmp: return InlineAssembly(byteCode, args); case ILCode.Ldarg: { if (methodDef.HasThis && ((ParameterDefinition)operand).Index < 0) { @@ -433,7 +435,7 @@ namespace ICSharpCode.Decompiler.Ast return MakeRef(new Ast.IdentifierExpression(((ParameterDefinition)operand).Name).WithAnnotation(operand)); } case ILCode.Ldc_I4: return AstBuilder.MakePrimitive((int)operand, byteCode.InferredType); - case ILCode.Ldc_I8: + case ILCode.Ldc_I8: return AstBuilder.MakePrimitive((long)operand, byteCode.InferredType); case ILCode.Ldc_R4: case ILCode.Ldc_R8: case ILCode.Ldc_Decimal: @@ -523,10 +525,49 @@ namespace ICSharpCode.Decompiler.Ast return new Ast.YieldBreakStatement(); case ILCode.YieldReturn: return new Ast.YieldStatement { Expression = arg1 }; + case ILCode.InitCollection: { + ObjectCreateExpression oce = (ObjectCreateExpression)arg1; + oce.Initializer = new ArrayInitializerExpression(); + for (int i = 1; i < args.Count; i++) { + ArrayInitializerExpression aie = args[i] as ArrayInitializerExpression; + if (aie != null && aie.Elements.Count == 1) + oce.Initializer.Elements.Add(aie.Elements.Single().Detach()); + else + oce.Initializer.Elements.Add(args[i]); + } + return oce; + } + case ILCode.InitCollectionAddMethod: { + var collectionInit = new ArrayInitializerExpression(); + collectionInit.Elements.AddRange(args); + return collectionInit; + } default: throw new Exception("Unknown OpCode: " + byteCode.Code); } } + Expression MakeDefaultValue(TypeReference type) + { + TypeDefinition typeDef = type.Resolve(); + if (typeDef != null) { + if (TypeAnalysis.IsIntegerOrEnum(typeDef)) + return AstBuilder.MakePrimitive(0, typeDef); + else if (!typeDef.IsValueType) + return new NullReferenceExpression(); + switch (typeDef.FullName) { + case "System.Nullable`1": + return new NullReferenceExpression(); + case "System.Single": + return new PrimitiveExpression(0f); + case "System.Double": + return new PrimitiveExpression(0.0); + case "System.Decimal": + return new PrimitiveExpression(0m); + } + } + return new DefaultValueExpression { Type = AstBuilder.ConvertType(type) }; + } + static AstNode TransformCall(bool isVirtual, object operand, MethodDefinition methodDef, List args) { Cecil.MethodReference cecilMethod = ((MethodReference)operand); @@ -619,7 +660,7 @@ namespace ICSharpCode.Decompiler.Ast } } // Default invocation - AdjustArgumentsForMethodCall(cecilMethod, methodArgs); + AdjustArgumentsForMethodCall(cecilMethodDef ?? cecilMethod, methodArgs); return target.Invoke(cecilMethod.Name, ConvertTypeArguments(cecilMethod), methodArgs).WithAnnotation(cecilMethod); } diff --git a/ICSharpCode.Decompiler/Ast/Transforms/ConvertConstructorCallIntoInitializer.cs b/ICSharpCode.Decompiler/Ast/Transforms/ConvertConstructorCallIntoInitializer.cs index daf9d868e..ec88a2699 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/ConvertConstructorCallIntoInitializer.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/ConvertConstructorCallIntoInitializer.cs @@ -50,15 +50,18 @@ namespace ICSharpCode.Decompiler.Ast.Transforms } }; + static readonly AstNode thisCallPattern = new ExpressionStatement(new ThisReferenceExpression().Invoke(".ctor", new Repeat(new AnyNode()))); + public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data) { var instanceCtors = typeDeclaration.Members.OfType().Where(c => (c.Modifiers & Modifiers.Static) == 0).ToArray(); - if (instanceCtors.Length > 0 && typeDeclaration.ClassType == NRefactory.TypeSystem.ClassType.Class) { + var instanceCtorsNotChainingWithThis = instanceCtors.Where(ctor => thisCallPattern.Match(ctor.Body.Statements.FirstOrDefault()) == null).ToArray(); + if (instanceCtorsNotChainingWithThis.Length > 0 && typeDeclaration.ClassType == NRefactory.TypeSystem.ClassType.Class) { // Recognize field initializers: // Convert first statement in all ctors (if all ctors have the same statement) into a field initializer. bool allSame; do { - Match m = fieldInitializerPattern.Match(instanceCtors[0].Body.FirstOrDefault()); + Match m = fieldInitializerPattern.Match(instanceCtorsNotChainingWithThis[0].Body.FirstOrDefault()); if (m == null) break; @@ -70,12 +73,12 @@ namespace ICSharpCode.Decompiler.Ast.Transforms break; allSame = true; - for (int i = 1; i < instanceCtors.Length; i++) { - if (instanceCtors[0].Body.First().Match(instanceCtors[i].Body.FirstOrDefault()) == null) + for (int i = 1; i < instanceCtorsNotChainingWithThis.Length; i++) { + if (instanceCtors[0].Body.First().Match(instanceCtorsNotChainingWithThis[i].Body.FirstOrDefault()) == null) allSame = false; } if (allSame) { - foreach (var ctor in instanceCtors) + foreach (var ctor in instanceCtorsNotChainingWithThis) ctor.Body.First().Remove(); fieldOrEventDecl.GetChildrenByRole(AstNode.Roles.Variable).Single().Initializer = m.Get("initializer").Single().Detach(); } diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs index 7684b1702..f264046f9 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs @@ -108,8 +108,8 @@ namespace ICSharpCode.Decompiler.Ast.Transforms 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; + // Anonymous methods are defined in the same assembly + MethodDefinition method = methodRef.ResolveWithinSameModule(); if (!IsAnonymousMethod(context, method)) return false; @@ -173,11 +173,11 @@ namespace ICSharpCode.Decompiler.Ast.Transforms if (stmt.Variables.Count() != 1) continue; var variable = stmt.Variables.Single(); - TypeDefinition type = stmt.Type.Annotation(); + TypeDefinition type = stmt.Type.Annotation().ResolveWithinSameModule(); if (!IsPotentialClosure(context, type)) continue; ObjectCreateExpression oce = variable.Initializer as ObjectCreateExpression; - if (oce == null || oce.Type.Annotation() != type || oce.Arguments.Any() || !oce.Initializer.IsNull) + if (oce == null || oce.Type.Annotation().ResolveWithinSameModule() != type || oce.Arguments.Any() || !oce.Initializer.IsNull) continue; // Looks like we found a display class creation. Now let's verify that the variable is used only for field accesses: bool ok = true; @@ -222,7 +222,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms isParameter = parameterOccurrances.Count(c => c == param) == 1; } if (isParameter) { - dict[m.Get("left").Single().Annotation()] = right; + dict[m.Get("left").Single().Annotation().ResolveWithinSameModule()] = right; cur.Remove(); } else { break; @@ -247,7 +247,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms if (identExpr.Identifier == variable.Name) { MemberReferenceExpression mre = (MemberReferenceExpression)identExpr.Parent; AstNode replacement; - if (dict.TryGetValue(mre.Annotation(), out replacement)) { + if (dict.TryGetValue(mre.Annotation().ResolveWithinSameModule(), out replacement)) { mre.ReplaceWith(replacement.Clone()); } } diff --git a/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs index 9e2dc5042..33786fcc2 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs @@ -2,6 +2,7 @@ // This code is distributed under MIT X11 license (for details please see \doc\license.txt) using System; +using System.Collections.Generic; using System.Linq; using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.CSharp.PatternMatching; @@ -31,6 +32,10 @@ namespace ICSharpCode.Decompiler.Ast.Transforms TransformForeach(compilationUnit); TransformFor(compilationUnit); TransformDoWhile(compilationUnit); + if (context.Settings.LockStatement) + TransformLock(compilationUnit); + if (context.Settings.SwitchStatementOnString) + TransformSwitchOnString(compilationUnit); if (context.Settings.AutomaticProperties) TransformAutomaticProperties(compilationUnit); if (context.Settings.AutomaticEvents) @@ -88,7 +93,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms public void TransformUsings(AstNode compilationUnit) { - foreach (AstNode node in compilationUnit.Descendants.ToArray()) { + foreach (AstNode node in compilationUnit.Descendants.OfType().ToArray()) { Match m1 = variableDeclPattern.Match(node); if (m1 == null) continue; AstNode tryCatch = node.NextSibling; @@ -179,7 +184,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms public void TransformForeach(AstNode compilationUnit) { - foreach (AstNode node in compilationUnit.Descendants.ToArray()) { + foreach (AstNode node in compilationUnit.Descendants.OfType().ToArray()) { Match m = foreachPattern.Match(node); if (m == null) continue; @@ -226,7 +231,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms public void TransformFor(AstNode compilationUnit) { - foreach (AstNode node in compilationUnit.Descendants.ToArray()) { + foreach (AstNode node in compilationUnit.Descendants.OfType().ToArray()) { Match m1 = variableDeclPattern.Match(node); if (m1 == null) continue; AstNode next = node.NextSibling; @@ -300,6 +305,201 @@ 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.OfType().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 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 VariableDeclarationStatement { + Type = new PrimitiveType("int"), + Variables = { new NamedNode("intVar", new VariableInitializer()) } + }, + new IfElseStatement { + Condition = new Backreference("cachedDict").ToExpression().Invoke( + "TryGetValue", + new NamedNode("switchVar", new IdentifierExpression()), + new DirectionExpression { + FieldDirection = FieldDirection.Out, + Expression = new IdentifierExpressionBackreference("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 void TransformSwitchOnString(AstNode compilationUnit) + { + foreach (AstNode node in compilationUnit.Descendants.OfType().ToArray()) { + Match m = switchOnStringPattern.Match(node); + if (m == null) + continue; + if (m.Has("nonNullDefaultStmt") && !m.Has("nullStmt")) + continue; + // 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().Match(m.Get("switchExpr").Single()) == null) { + AssignmentExpression assign = m.Get("switchExpr").Single() as AssignmentExpression; + if (m.Get("switchVar").Single().Match(assign.Left) == null) + continue; + } + FieldReference cachedDictField = m.Get("cachedDict").Single().Annotation(); + if (cachedDictField == null || !cachedDictField.DeclaringType.Name.StartsWith("", StringComparison.Ordinal)) + continue; + 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); + if (m.Has("nonNullDefaultStmt")) { + section = new SwitchSection(); + section.CaseLabels.Add(new CaseLabel()); + 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); + } + } + + List> BuildDictionary(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; + PrimitiveExpression arg1 = ie.Arguments.ElementAtOrDefault(0) as PrimitiveExpression; + PrimitiveExpression arg2 = ie.Arguments.ElementAtOrDefault(1) as PrimitiveExpression; + if (arg1 != null && arg2 != null && arg1.Value is string && arg2.Value is int) + dict.Add(new KeyValuePair((string)arg1.Value, (int)arg2.Value)); + } + return dict; + } + #endregion + #region Automatic Properties static readonly PropertyDeclaration automaticPropertyPattern = new PropertyDeclaration { Attributes = { new Repeat(new AnyNode()) }, @@ -361,7 +561,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms #endregion #region Automatic Events - Accessor automaticEventPatternV4 = new Accessor { + static readonly Accessor automaticEventPatternV4 = new Accessor { Body = new BlockStatement { new VariableDeclarationStatement { Type = new AnyNode("type"), @@ -391,7 +591,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 +619,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 +651,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 21d5efb55..23662777f 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs @@ -205,9 +205,16 @@ namespace ICSharpCode.Decompiler.Ast.Transforms return null; } - bool IsWithoutSideEffects(Expression left) + static bool IsWithoutSideEffects(Expression left) { - return left is IdentifierExpression; // TODO + if (left is ThisReferenceExpression) + return true; + if (left is IdentifierExpression) + return true; + MemberReferenceExpression mre = left as MemberReferenceExpression; + if (mre != null) + return mre.Annotation() != null && IsWithoutSideEffects(mre.Target); + return false; } void IAstTransform.Run(AstNode node) diff --git a/ICSharpCode.Decompiler/CecilExtensions.cs b/ICSharpCode.Decompiler/CecilExtensions.cs index 2280eba14..38e21e100 100644 --- a/ICSharpCode.Decompiler/CecilExtensions.cs +++ b/ICSharpCode.Decompiler/CecilExtensions.cs @@ -159,6 +159,14 @@ namespace ICSharpCode.Decompiler return accessorMethods; } + public static TypeDefinition ResolveWithinSameModule(this TypeReference type) + { + if (type != null && type.GetElementType().Module == type.Module) + return type.Resolve(); + else + return null; + } + public static FieldDefinition ResolveWithinSameModule(this FieldReference field) { if (field != null && field.DeclaringType.GetElementType().Module == field.Module) diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 82e6797b6..807832eaf 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -101,6 +101,33 @@ namespace ICSharpCode.Decompiler } } + bool lockStatement = true; + + /// + /// Decompile lock statements. + /// + public bool LockStatement { + get { return lockStatement; } + set { + if (lockStatement != value) { + lockStatement = value; + OnPropertyChanged("LockStatement"); + } + } + } + + bool switchStatementOnString = true; + + public bool SwitchStatementOnString { + get { return switchStatementOnString; } + set { + if (switchStatementOnString != value) { + switchStatementOnString = value; + OnPropertyChanged("SwitchStatementOnString"); + } + } + } + public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 7e6f812aa..3c00e0966 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -89,7 +89,7 @@ - + diff --git a/ICSharpCode.Decompiler/ILAst/ArrayInitializers.cs b/ICSharpCode.Decompiler/ILAst/ArrayInitializers.cs deleted file mode 100644 index 961a58aa2..000000000 --- a/ICSharpCode.Decompiler/ILAst/ArrayInitializers.cs +++ /dev/null @@ -1,103 +0,0 @@ -// 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.Linq; - -using Mono.Cecil; - -namespace ICSharpCode.Decompiler.ILAst -{ - /// - /// IL AST transformation that introduces array initializers. - /// - public static class ArrayInitializers - { - public static PeepholeTransform Transform(ILBlock method) - { - var newArrPattern = new StoreToVariable(new ILExpression(ILCode.Newarr, ILExpression.AnyOperand, new ILExpression(ILCode.Ldc_I4, ILExpression.AnyOperand))); - var arg1 = new StoreToVariable(new LoadFromVariable(newArrPattern)) { MustBeGenerated = true }; - var arg2 = new StoreToVariable(new LoadFromVariable(newArrPattern)) { MustBeGenerated = true }; - var initializeArrayPattern = new ILCall( - "System.Runtime.CompilerServices.RuntimeHelpers", "InitializeArray", - new LoadFromVariable(arg1), new ILExpression(ILCode.Ldtoken, ILExpression.AnyOperand)); - - return delegate(ILBlock block, ref int i) { - if (!newArrPattern.Match(block.Body[i])) - return; - ILExpression newArrInst = ((ILExpression)block.Body[i]).Arguments[0]; - int arrayLength = (int)newArrInst.Arguments[0].Operand; - if (arrayLength == 0) - return; - if (arg1.Match(block.Body.ElementAtOrDefault(i + 1)) && arg2.Match(block.Body.ElementAtOrDefault(i + 2))) { - if (initializeArrayPattern.Match(block.Body.ElementAtOrDefault(i + 3))) { - if (HandleStaticallyInitializedArray(arg2, block, i, newArrInst, arrayLength)) { - i -= new ILInlining(method).InlineInto(block, i + 1) - 1; - } - return; - } - } - if (i + 1 + arrayLength > block.Body.Count) - return; - List operands = new List(); - for (int j = 0; j < arrayLength; j++) { - ILExpression expr = block.Body[i + 1 + j] as ILExpression; - if (expr == null || !IsStoreToArray(expr.Code)) - break; - if (!(expr.Arguments[0].Code == ILCode.Ldloc && expr.Arguments[0].Operand == newArrPattern.LastVariable)) - break; - if (!(expr.Arguments[1].Code == ILCode.Ldc_I4 && (int)expr.Arguments[1].Operand == j)) - break; - operands.Add(expr.Arguments[2]); - } - if (operands.Count == arrayLength) { - ((ILExpression)block.Body[i]).Arguments[0] = new ILExpression( - ILCode.InitArray, newArrInst.Operand, operands.ToArray()); - block.Body.RemoveRange(i + 1, arrayLength); - i -= new ILInlining(method).InlineInto(block, i + 1) - 1; - } - }; - } - - static bool IsStoreToArray(ILCode code) - { - switch (code) { - case ILCode.Stelem_Any: - case ILCode.Stelem_I: - case ILCode.Stelem_I1: - case ILCode.Stelem_I2: - case ILCode.Stelem_I4: - case ILCode.Stelem_I8: - case ILCode.Stelem_R4: - case ILCode.Stelem_R8: - case ILCode.Stelem_Ref: - return true; - default: - return false; - } - } - - static bool HandleStaticallyInitializedArray(StoreToVariable arg2, ILBlock block, int i, ILExpression newArrInst, int arrayLength) - { - FieldDefinition field = ((ILExpression)block.Body[i + 3]).Arguments[1].Operand as FieldDefinition; - if (field == null || field.InitialValue == null) - return false; - switch (TypeAnalysis.GetTypeCode(newArrInst.Operand as TypeReference)) { - case TypeCode.Int32: - case TypeCode.UInt32: - if (field.InitialValue.Length == arrayLength * 4) { - ILExpression[] newArr = new ILExpression[arrayLength]; - for (int j = 0; j < newArr.Length; j++) { - newArr[j] = new ILExpression(ILCode.Ldc_I4, BitConverter.ToInt32(field.InitialValue, j * 4)); - } - block.Body[i] = new ILExpression(ILCode.Stloc, arg2.LastVariable, new ILExpression(ILCode.InitArray, newArrInst.Operand, newArr)); - block.Body.RemoveRange(i + 1, 3); - return true; - } - break; - } - return false; - } - } -} diff --git a/ICSharpCode.Decompiler/ILAst/ILCodes.cs b/ICSharpCode.Decompiler/ILAst/ILCodes.cs index 3dded5bfa..bbc45023a 100644 --- a/ICSharpCode.Decompiler/ILAst/ILCodes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILCodes.cs @@ -259,12 +259,15 @@ namespace ICSharpCode.Decompiler.ILAst LogicAnd, LogicOr, InitArray, // Array Initializer + InitCollection, // Collection Initializer: first arg is newobj, remaining args are InitCollectionAddMethod method calls + InitCollectionAddMethod, TernaryOp, // ?: LoopOrSwitchBreak, LoopContinue, Ldc_Decimal, YieldBreak, YieldReturn, + DefaultValue, // default(T) Pattern // used for ILAst pattern nodes } diff --git a/ICSharpCode.Decompiler/ILAst/ILInlining.cs b/ICSharpCode.Decompiler/ILAst/ILInlining.cs index bc2430dfd..26e26c6ae 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(); @@ -25,6 +25,17 @@ namespace ICSharpCode.Decompiler.ILAst public ILInlining(ILBlock method) { this.method = method; + AnalyzeMethod(); + } + + void AnalyzeMethod() + { + numStloc.Clear(); + numLdloc.Clear(); + numLdloca.Clear(); + numStarg.Clear(); + numLdarga.Clear(); + // Analyse the whole method foreach(ILExpression expr in method.GetSelfAndChildrenRecursive()) { ILVariable locVar = expr.Operand as ILVariable; @@ -64,7 +75,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++; @@ -76,7 +87,7 @@ namespace ICSharpCode.Decompiler.ILAst /// Inlines instructions before pos into block.Body[pos]. /// /// The number of instructions that were inlined. - public int InlineInto(ILBlock block, int pos) + public int InlineInto(ILBlock block, int pos, bool aggressive) { if (pos >= block.Body.Count) return 0; @@ -85,7 +96,7 @@ namespace ICSharpCode.Decompiler.ILAst ILExpression expr = block.Body[pos] as ILExpression; if (expr == null || expr.Code != ILCode.Stloc) break; - if (InlineIfPossible(block, pos)) + if (InlineOneIfPossible(block, pos, aggressive)) count++; else break; @@ -93,10 +104,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 = true) + public bool InlineOneIfPossible(ILBlock block, int pos, bool aggressive) { ILVariable v; ILExpression inlinedExpression; @@ -120,6 +147,12 @@ namespace ICSharpCode.Decompiler.ILAst // ensure the variable is accessed only a single time if (!(numStloc.GetOrDefault(v) == 1 && numLdloc.GetOrDefault(v) == 1 && numLdloca.GetOrDefault(v) == 0)) return false; + + if (next is ILCondition) + next = ((ILCondition)next).Condition; + else if (next is ILWhileLoop) + next = ((ILWhileLoop)next).Condition; + ILExpression parent; int pos; if (FindLoadInNext(next as ILExpression, v, inlinedExpression, out parent, out pos) == true) { @@ -150,6 +183,16 @@ namespace ICSharpCode.Decompiler.ILAst } } + /// + /// Gets whether 'expressionBeingMoved' can be inlined into 'expr'. + /// + public bool CanInlineInto(ILExpression expr, ILVariable v, ILExpression expressionBeingMoved) + { + ILExpression parent; + int pos; + return FindLoadInNext(expr, v, expressionBeingMoved, out parent, out pos) == true; + } + /// /// Finds the position to inline to. /// @@ -177,6 +220,17 @@ namespace ICSharpCode.Decompiler.ILAst if (r != null) return r; } + if (IsSafeForInlineOver(expr, expressionBeingMoved)) + return null; // continue searching + else + return false; // abort, inlining not possible + } + + /// + /// Determines whether it is save to move 'expressionBeingMoved' past 'expr' + /// + bool IsSafeForInlineOver(ILExpression expr, ILExpression expressionBeingMoved) + { switch (expr.Code) { case ILCode.Ldloc: ILVariable loadedVar = (ILVariable)expr.Operand; @@ -188,9 +242,8 @@ namespace ICSharpCode.Decompiler.ILAst if (potentialStore.Code == ILCode.Stloc && potentialStore.Operand == loadedVar) return false; } - // the expression is loading a non-forbidden variable: - // we're allowed to continue searching - return null; + // the expression is loading a non-forbidden variable + return true; case ILCode.Ldarg: // Also try moving over ldarg instructions - this is necessary because an earlier copy propagation // step might have introduced ldarg in place of an ldloc that would be skipped. @@ -201,12 +254,18 @@ namespace ICSharpCode.Decompiler.ILAst if (potentialStore.Code == ILCode.Starg && potentialStore.Operand == loadedParam) return false; } - return null; + return true; case ILCode.Ldloca: case ILCode.Ldarga: - // Continue searching: - // It is always safe to move code past an instruction that loads a constant. - return null; + case ILCode.Ldflda: + case ILCode.Ldsflda: + case ILCode.Ldelema: + // address-loading instructions are safe if their arguments are safe + foreach (ILExpression arg in expr.Arguments) { + if (!IsSafeForInlineOver(arg, expressionBeingMoved)) + return false; + } + return true; default: // abort, inlining is not possible return false; @@ -223,24 +282,50 @@ namespace ICSharpCode.Decompiler.ILAst /// public void CopyPropagation() { + // Perform 'dup' removal prior to copy propagation: + foreach (ILExpression expr in method.GetSelfAndChildrenRecursive()) { + for (int i = 0; i < expr.Arguments.Count; i++) { + if (expr.Arguments[i].Code == ILCode.Dup) { + ILExpression child = expr.Arguments[i].Arguments[0]; + child.ILRanges.AddRange(expr.Arguments[i].ILRanges); + expr.Arguments[i] = child; + } + } + } + foreach (ILBlock block in method.GetSelfAndChildrenRecursive()) { - for (int i = block.Body.Count - 1; i >= 0; i--) { + for (int i = 0; i < block.Body.Count; i++) { ILVariable v; ILExpression ldArg; if (block.Body[i].Match(ILCode.Stloc, out v, out ldArg) && numStloc.GetOrDefault(v) == 1 && numLdloca.GetOrDefault(v) == 0 && CanPerformCopyPropagation(ldArg)) { + // un-inline the arguments of the ldArg instruction + ILVariable[] uninlinedArgs = new ILVariable[ldArg.Arguments.Count]; + for (int j = 0; j < uninlinedArgs.Length; j++) { + uninlinedArgs[j] = new ILVariable { IsGenerated = true, Name = v.Name + "_cp_" + j }; + block.Body.Insert(i++, new ILExpression(ILCode.Stloc, uninlinedArgs[j], ldArg.Arguments[j])); + } + // perform copy propagation: foreach (var expr in method.GetSelfAndChildrenRecursive()) { if (expr.Code == ILCode.Ldloc && expr.Operand == v) { expr.Code = ldArg.Code; expr.Operand = ldArg.Operand; + for (int j = 0; j < uninlinedArgs.Length; j++) { + expr.Arguments.Add(new ILExpression(ILCode.Ldloc, uninlinedArgs[j])); + } } } block.Body.RemoveAt(i); - InlineInto(block, i); // maybe inlining gets possible after the removal of block.Body[i] + if (uninlinedArgs.Length > 0) { + // if we un-inlined stuff; we need to update the usage counters + AnalyzeMethod(); + } + InlineInto(block, i, aggressive: false); // maybe inlining gets possible after the removal of block.Body[i] + i -= uninlinedArgs.Length + 1; } } } @@ -251,7 +336,12 @@ namespace ICSharpCode.Decompiler.ILAst switch (ldArg.Code) { case ILCode.Ldloca: case ILCode.Ldarga: - return true; // ldloca/ldarga always return the same value for a given operand, so they can be safely copied + case ILCode.Ldelema: + case ILCode.Ldflda: + case ILCode.Ldsflda: + // All address-loading instructions always return the same value for a given operand/argument combination, + // so they can be safely copied. + return true; case ILCode.Ldarg: // arguments can be copied only if they aren't assigned to (directly or indirectly via ldarga) ParameterDefinition pd = (ParameterDefinition)ldArg.Operand; diff --git a/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs new file mode 100644 index 000000000..e4620bced --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs @@ -0,0 +1,213 @@ +// 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.Linq; + +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.ILAst +{ + /// + /// IL AST transformation that introduces array, object and collection initializers. + /// + public class InitializerPeepholeTransforms + { + readonly ILBlock method; + + #region Array Initializers + StoreToVariable newArrPattern; + ILCall initializeArrayPattern; + + public InitializerPeepholeTransforms(ILBlock method) + { + this.method = method; + newArrPattern = new StoreToVariable(new ILExpression( + ILCode.Newarr, ILExpression.AnyOperand, new ILExpression(ILCode.Ldc_I4, ILExpression.AnyOperand))); + initializeArrayPattern = new ILCall( + "System.Runtime.CompilerServices.RuntimeHelpers", "InitializeArray", + new LoadFromVariable(newArrPattern), new ILExpression(ILCode.Ldtoken, ILExpression.AnyOperand)); + } + + public void TransformArrayInitializers(ILBlock block, ref int i) + { + if (!newArrPattern.Match(block.Body[i])) + return; + ILExpression newArrInst = ((ILExpression)block.Body[i]).Arguments[0]; + int arrayLength = (int)newArrInst.Arguments[0].Operand; + if (arrayLength == 0) + return; + if (initializeArrayPattern.Match(block.Body.ElementAtOrDefault(i + 1))) { + if (HandleStaticallyInitializedArray(newArrPattern, block, i, newArrInst, arrayLength)) { + i -= new ILInlining(method).InlineInto(block, i + 1, aggressive: true) - 1; + } + return; + } + List operands = new List(); + int numberOfInstructionsToRemove = 0; + for (int j = i + 1; j < block.Body.Count; j++) { + ILExpression expr = block.Body[j] as ILExpression; + if (expr == null || !IsStoreToArray(expr.Code)) + break; + if (!(expr.Arguments[0].Code == ILCode.Ldloc && expr.Arguments[0].Operand == newArrPattern.LastVariable)) + break; + if (expr.Arguments[1].Code != ILCode.Ldc_I4) + break; + int pos = (int)expr.Arguments[1].Operand; + const int maxConsecutiveDefaultValueExpressions = 10; + if (pos < operands.Count || pos > operands.Count + maxConsecutiveDefaultValueExpressions) + break; + while (operands.Count < pos) + operands.Add(new ILExpression(ILCode.DefaultValue, newArrInst.Operand)); + operands.Add(expr.Arguments[2]); + numberOfInstructionsToRemove++; + } + if (operands.Count == arrayLength) { + ((ILExpression)block.Body[i]).Arguments[0] = new ILExpression( + ILCode.InitArray, newArrInst.Operand, operands.ToArray()); + block.Body.RemoveRange(i + 1, numberOfInstructionsToRemove); + i -= new ILInlining(method).InlineInto(block, i + 1, aggressive: true) - 1; + } + } + + static bool IsStoreToArray(ILCode code) + { + switch (code) { + case ILCode.Stelem_Any: + case ILCode.Stelem_I: + case ILCode.Stelem_I1: + case ILCode.Stelem_I2: + case ILCode.Stelem_I4: + case ILCode.Stelem_I8: + case ILCode.Stelem_R4: + case ILCode.Stelem_R8: + case ILCode.Stelem_Ref: + return true; + default: + return false; + } + } + + static bool HandleStaticallyInitializedArray(StoreToVariable newArrPattern, ILBlock block, int i, ILExpression newArrInst, int arrayLength) + { + FieldDefinition field = ((ILExpression)block.Body[i + 1]).Arguments[1].Operand as FieldDefinition; + if (field == null || field.InitialValue == null) + return false; + ILExpression[] newArr = new ILExpression[arrayLength]; + if (DecodeArrayInitializer(TypeAnalysis.GetTypeCode(newArrInst.Operand as TypeReference), field.InitialValue, newArr)) { + block.Body[i] = new ILExpression(ILCode.Stloc, newArrPattern.LastVariable, new ILExpression(ILCode.InitArray, newArrInst.Operand, newArr)); + block.Body.RemoveAt(i + 1); + return true; + } + return false; + } + + static bool DecodeArrayInitializer(TypeCode elementType, byte[] initialValue, ILExpression[] output) + { + switch (elementType) { + case TypeCode.Boolean: + case TypeCode.SByte: + case TypeCode.Byte: + if (initialValue.Length == output.Length) { + for (int j = 0; j < output.Length; j++) { + output[j] = new ILExpression(ILCode.Ldc_I4, (int)initialValue[j]); + } + return true; + } + return false; + case TypeCode.Char: + case TypeCode.Int16: + case TypeCode.UInt16: + if (initialValue.Length == output.Length * 2) { + for (int j = 0; j < output.Length; j++) { + output[j] = new ILExpression(ILCode.Ldc_I4, (int)BitConverter.ToInt16(initialValue, j * 2)); + } + return true; + } + return false; + case TypeCode.Int32: + case TypeCode.UInt32: + if (initialValue.Length == output.Length * 4) { + for (int j = 0; j < output.Length; j++) { + output[j] = new ILExpression(ILCode.Ldc_I4, BitConverter.ToInt32(initialValue, j * 4)); + } + return true; + } + return false; + case TypeCode.Int64: + case TypeCode.UInt64: + if (initialValue.Length == output.Length * 8) { + for (int j = 0; j < output.Length; j++) { + output[j] = new ILExpression(ILCode.Ldc_I8, BitConverter.ToInt64(initialValue, j * 8)); + } + return true; + } + return false; + case TypeCode.Single: + if (initialValue.Length == output.Length * 4) { + for (int j = 0; j < output.Length; j++) { + output[j] = new ILExpression(ILCode.Ldc_R4, BitConverter.ToSingle(initialValue, j * 4)); + } + return true; + } + return false; + case TypeCode.Double: + if (initialValue.Length == output.Length * 8) { + for (int j = 0; j < output.Length; j++) { + output[j] = new ILExpression(ILCode.Ldc_R8, BitConverter.ToDouble(initialValue, j * 8)); + } + return true; + } + return false; + default: + return false; + } + } + #endregion + + #region Collection Initilializers + public void TransformCollectionInitializers(ILBlock block, ref int i) + { + ILVariable v; + ILExpression expr; + if (!block.Body[i].Match(ILCode.Stloc, out v, out expr) || expr.Code != ILCode.Newobj) + return; + MethodReference ctor = (MethodReference)expr.Operand; + TypeDefinition td = ctor.DeclaringType.Resolve(); + if (td == null || !td.Interfaces.Any(intf => intf.Name == "IEnumerable" && intf.Namespace == "System.Collections")) + return; + // This is a collection: we can convert Add() calls into a collection initializer + ILExpression collectionInitializer = new ILExpression(ILCode.InitCollection, null, expr); + for (int j = i + 1; j < block.Body.Count; j++) { + MethodReference addMethod; + List args; + if (!block.Body[j].Match(ILCode.Callvirt, out addMethod, out args)) + break; + if (addMethod.Name != "Add" || !addMethod.HasThis || args.Count < 2 || args[0].Code != ILCode.Ldloc || args[0].Operand != v) + break; + collectionInitializer.Arguments.Add((ILExpression)block.Body[j]); + } + // ensure we added at least one additional arg to the collection initializer: + if (collectionInitializer.Arguments.Count == 1) + return; + ILInlining inline = new ILInlining(method); + ILExpression followingExpr = block.Body.ElementAtOrDefault(i + collectionInitializer.Arguments.Count) as ILExpression; + if (inline.CanInlineInto(followingExpr, v, collectionInitializer)) { + block.Body.RemoveRange(i + 1, collectionInitializer.Arguments.Count - 1); + ((ILExpression)block.Body[i]).Arguments[0] = collectionInitializer; + + // Change add methods into InitCollectionAddMethod: + for (int j = 1; j < collectionInitializer.Arguments.Count; j++) { + collectionInitializer.Arguments[j].Arguments.RemoveAt(0); + collectionInitializer.Arguments[j].Code = ILCode.InitCollectionAddMethod; + } + + inline = new ILInlining(method); // refresh variable usage info + if (inline.InlineIfPossible(block, ref i)) + i++; // retry transformations on the new combined instruction + } + } + #endregion + } +} diff --git a/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs index bf5dd5d4d..4e79c0397 100644 --- a/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs +++ b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs @@ -25,13 +25,16 @@ namespace ICSharpCode.Decompiler.ILAst transforms.context = context; transforms.method = method; + InitializerPeepholeTransforms initializerTransforms = new InitializerPeepholeTransforms(method); PeepholeTransform[] blockTransforms = { - ArrayInitializers.Transform(method), - transforms.CachedDelegateInitialization + initializerTransforms.TransformArrayInitializers, + initializerTransforms.TransformCollectionInitializers, + transforms.CachedDelegateInitialization, + transforms.MakeAssignmentExpression }; Func[] exprTransforms = { - EliminateDups, - HandleDecimalConstants + HandleDecimalConstants, + SimplifyLdObjAndStObj }; // Traverse in post order so that nested blocks are transformed first. This is required so that // patterns on the parent block can assume that all nested blocks are already transformed. @@ -45,9 +48,15 @@ namespace ICSharpCode.Decompiler.ILAst expr = block.Body[i] as ILExpression; if (expr != null) { // apply expr transforms to top-level expr in block - foreach (var t in exprTransforms) - expr = t(expr); + bool modified = ApplyExpressionTransforms(ref expr, exprTransforms); block.Body[i] = expr; + if (modified) { + ILInlining inlining = new ILInlining(method); + if (inlining.InlineIfPossible(block, ref i)) { + i++; // retry all transforms on the new combined instruction + continue; + } + } } // apply block transforms foreach (var t in blockTransforms) { @@ -63,20 +72,29 @@ namespace ICSharpCode.Decompiler.ILAst // apply expr transforms to all arguments for (int i = 0; i < expr.Arguments.Count; i++) { ILExpression arg = expr.Arguments[i]; - foreach (var t in exprTransforms) - arg = t(arg); + ApplyExpressionTransforms(ref arg, exprTransforms); expr.Arguments[i] = arg; } } } } - static ILExpression EliminateDups(ILExpression expr) + static bool ApplyExpressionTransforms(ref ILExpression expr, Func[] exprTransforms) { - if (expr.Code == ILCode.Dup) - return expr.Arguments.Single(); - else - return expr; + bool modifiedInAnyIteration = false; + bool modified; + do { + modified = false; + ILExpression oldExpr = expr; + ILCode oldOpCode = oldExpr.Code; + foreach (var t in exprTransforms) + expr = t(expr); + if (expr != oldExpr || oldOpCode != expr.Code) { + modified = true; + modifiedInAnyIteration = true; + } + } while (modified); + return modifiedInAnyIteration; } #region HandleDecimalConstants @@ -120,6 +138,54 @@ namespace ICSharpCode.Decompiler.ILAst } #endregion + #region SimplifyLdObjAndStObj + static ILExpression SimplifyLdObjAndStObj(ILExpression expr) + { + if (expr.Code == ILCode.Initobj) { + expr.Code = ILCode.Stobj; + expr.Arguments.Add(new ILExpression(ILCode.DefaultValue, expr.Operand)); + } + if (expr.Code == ILCode.Stobj) { + switch (expr.Arguments[0].Code) { + case ILCode.Ldelema: + return SimplifyLdObjOrStObj(expr, ILCode.Stelem_Any); + case ILCode.Ldloca: + return SimplifyLdObjOrStObj(expr, ILCode.Stloc); + case ILCode.Ldarga: + return SimplifyLdObjOrStObj(expr, ILCode.Starg); + case ILCode.Ldflda: + return SimplifyLdObjOrStObj(expr, ILCode.Stfld); + case ILCode.Ldsflda: + return SimplifyLdObjOrStObj(expr, ILCode.Stsfld); + } + } else if (expr.Code == ILCode.Ldobj) { + switch (expr.Arguments[0].Code) { + case ILCode.Ldelema: + return SimplifyLdObjOrStObj(expr, ILCode.Ldelem_Any); + case ILCode.Ldloca: + return SimplifyLdObjOrStObj(expr, ILCode.Ldloc); + case ILCode.Ldarga: + return SimplifyLdObjOrStObj(expr, ILCode.Ldarg); + case ILCode.Ldflda: + return SimplifyLdObjOrStObj(expr, ILCode.Ldfld); + case ILCode.Ldsflda: + return SimplifyLdObjOrStObj(expr, ILCode.Ldsfld); + } + } + return expr; + } + + static ILExpression SimplifyLdObjOrStObj(ILExpression expr, ILCode newCode) + { + ILExpression lda = expr.Arguments[0]; + lda.Code = newCode; + if (expr.Code == ILCode.Stobj) + lda.Arguments.Add(expr.Arguments[1]); + lda.ILRanges.AddRange(expr.ILRanges); + return lda; + } + #endregion + #region CachedDelegateInitialization void CachedDelegateInitialization(ILBlock block, ref int i) { @@ -139,11 +205,11 @@ namespace ICSharpCode.Decompiler.ILAst ILExpression condition = c.Condition.Arguments.Single() as ILExpression; if (condition == null || condition.Code != ILCode.Ldsfld) return; - FieldDefinition field = condition.Operand as FieldDefinition; // field is defined in current assembly + FieldDefinition field = ((FieldReference)condition.Operand).ResolveWithinSameModule(); // field is defined in current assembly if (field == null || !field.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) return; ILExpression stsfld = c.TrueBlock.Body[0] as ILExpression; - if (!(stsfld != null && stsfld.Code == ILCode.Stsfld && stsfld.Operand == field)) + if (!(stsfld != null && stsfld.Code == ILCode.Stsfld && ((FieldReference)stsfld.Operand).ResolveWithinSameModule() == field)) return; ILExpression newObj = stsfld.Arguments[0]; if (!(newObj.Code == ILCode.Newobj && newObj.Arguments.Count == 2)) @@ -152,18 +218,20 @@ namespace ICSharpCode.Decompiler.ILAst return; if (newObj.Arguments[1].Code != ILCode.Ldftn) return; - MethodDefinition anonymousMethod = newObj.Arguments[1].Operand as MethodDefinition; // method is defined in current assembly + MethodDefinition anonymousMethod = ((MethodReference)newObj.Arguments[1].Operand).ResolveWithinSameModule(); // method is defined in current assembly if (!Ast.Transforms.DelegateConstruction.IsAnonymousMethod(context, anonymousMethod)) return; - ILExpression expr = block.Body.ElementAtOrDefault(i + 1) as ILExpression; - if (expr != null && expr.GetSelfAndChildrenRecursive().Count(e => e.Code == ILCode.Ldsfld && e.Operand == field) == 1) { - foreach (ILExpression parent in expr.GetSelfAndChildrenRecursive()) { + ILNode followingNode = block.Body.ElementAtOrDefault(i + 1); + if (followingNode != null && followingNode.GetSelfAndChildrenRecursive().Count( + e => e.Code == ILCode.Ldsfld && ((FieldReference)e.Operand).ResolveWithinSameModule() == field) == 1) + { + foreach (ILExpression parent in followingNode.GetSelfAndChildrenRecursive()) { for (int j = 0; j < parent.Arguments.Count; j++) { - if (parent.Arguments[j].Code == ILCode.Ldsfld && parent.Arguments[j].Operand == field) { + if (parent.Arguments[j].Code == ILCode.Ldsfld && ((FieldReference)parent.Arguments[j].Operand).ResolveWithinSameModule() == field) { parent.Arguments[j] = newObj; block.Body.RemoveAt(i); - i -= new ILInlining(method).InlineInto(block, i); + i -= new ILInlining(method).InlineInto(block, i, aggressive: true); return; } } @@ -171,5 +239,62 @@ 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 store2 = block.Body.ElementAtOrDefault(i + 2) as ILExpression; + if (StoreCanBeConvertedToAssignment(store2, exprVar)) { + // expr_44 = ... + // stloc(v1, expr_44) + // anystore(v2, expr_44) + // -> + // stloc(v1, anystore(v2, ...)) + inlining = new ILInlining(method); + if (inlining.numLdloc.GetOrDefault(exprVar) == 2 && inlining.numStloc.GetOrDefault(exprVar) == 1) { + block.Body.RemoveAt(i + 2); // remove store2 + block.Body.RemoveAt(i); // remove expr = ... + stloc1.Arguments[0] = store2; + store2.Arguments[store2.Arguments.Count - 1] = 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 + } + } + + bool StoreCanBeConvertedToAssignment(ILExpression store, ILVariable exprVar) + { + if (store != null && (store.Code == ILCode.Stloc || store.Code == ILCode.Stfld || store.Code == ILCode.Stsfld)) { + return store.Arguments.Last().Code == ILCode.Ldloc && store.Arguments.Last().Operand == exprVar; + } + return false; + } + #endregion } } diff --git a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs index 9c211f83d..063424e98 100644 --- a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs +++ b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs @@ -203,6 +203,18 @@ namespace ICSharpCode.Decompiler.ILAst } return ctor.DeclaringType; } + case ILCode.InitCollection: + return InferTypeForExpression(expr.Arguments[0], expectedType); + case ILCode.InitCollectionAddMethod: + { + MethodReference addMethod = (MethodReference)expr.Operand; + if (forceInferChildren) { + for (int i = 1; i < addMethod.Parameters.Count; i++) { + InferTypeForExpression(expr.Arguments[i-1], SubstituteTypeArgs(addMethod.Parameters[i].ParameterType, addMethod)); + } + } + return addMethod.DeclaringType; + } #endregion #region Load/Store Fields case ILCode.Ldfld: @@ -219,11 +231,11 @@ namespace ICSharpCode.Decompiler.ILAst InferTypeForExpression(expr.Arguments[0], ((FieldReference)expr.Operand).DeclaringType); InferTypeForExpression(expr.Arguments[1], GetFieldType((FieldReference)expr.Operand)); } - return null; + return GetFieldType((FieldReference)expr.Operand); case ILCode.Stsfld: if (forceInferChildren) InferTypeForExpression(expr.Arguments[0], GetFieldType((FieldReference)expr.Operand)); - return null; + return GetFieldType((FieldReference)expr.Operand); #endregion #region Reference/Pointer instructions case ILCode.Ldind_I: @@ -260,6 +272,8 @@ namespace ICSharpCode.Decompiler.ILAst return null; case ILCode.Initobj: return null; + case ILCode.DefaultValue: + return (TypeReference)expr.Operand; case ILCode.Localloc: if (forceInferChildren) { InferTypeForExpression(expr.Arguments[0], typeSystem.Int32); 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/ArrayInitializers.cs b/ICSharpCode.Decompiler/Tests/ArrayInitializers.cs deleted file mode 100644 index 9f180a8b4..000000000 --- a/ICSharpCode.Decompiler/Tests/ArrayInitializers.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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; - -public class ArrayInitializers -{ - // Helper methods used to ensure array initializers used within expressions work correctly - static void X(object a, object b) - { - } - - static object Y() - { - return null; - } - - public static void Array1() - { - X(Y(), new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); - } - - public static void Array2(int a, int b, int c) - { - X(Y(), new int[] { a, b, c }); - } - - public static void NestedArray(int a, int b, int c) - { - X(Y(), new int[][] { - new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, - new int[] { a, b, c }, - new int[] { 1, 2, 3, 4, 5, 6 } - }); - } -} diff --git a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj index 1f8fbd5be..556c24fef 100644 --- a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj @@ -53,6 +53,7 @@ + @@ -68,7 +69,7 @@ - + diff --git a/ICSharpCode.Decompiler/Tests/InitializerTests.cs b/ICSharpCode.Decompiler/Tests/InitializerTests.cs new file mode 100644 index 000000000..707765dcf --- /dev/null +++ b/ICSharpCode.Decompiler/Tests/InitializerTests.cs @@ -0,0 +1,118 @@ +// 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 class InitializerTests +{ + // Helper methods used to ensure initializers used within expressions work correctly + static void X(object a, object b) + { + } + + static object Y() + { + return null; + } + + #region Array Initializers + public static void Array1() + { + X(Y(), new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); + } + + public static void Array2(int a, int b, int c) + { + X(Y(), new int[] { a, 0, b, 0, c }); + } + + public static void NestedArray(int a, int b, int c) + { + X(Y(), new int[][] { + new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, + new int[] { a, b, c }, + new int[] { 1, 2, 3, 4, 5, 6 } + }); + } + + public static void ArrayBoolean() + { + X(Y(), new bool[] { true, false, true, false, false, false, true, true }); + } + + public static void ArrayByte() + { + X(Y(), new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 254, 255 }); + } + + public static void ArraySByte() + { + X(Y(), new sbyte[] { -128, -127, 0, 1, 2, 3, 4, 127 }); + } + + public static void ArrayShort() + { + X(Y(), new short[] { -32768, -1, 0, 1, 32767 }); + } + + public static void ArrayUShort() + { + X(Y(), new ushort[] { 0, 1, 32767, 32768, 65534, 65535 }); + } + + public static void ArrayInt() + { + X(Y(), new int[] { 1, -2, 2000000000, 4, 5, -6, 7, 8, 9, 10 }); + } + + public static void ArrayUInt() + { + X(Y(), new uint[] { 1, 2000000000, 3000000000, 4, 5, 6, 7, 8, 9, 10 }); + } + + public static void ArrayLong() + { + X(Y(), new long[] { -4999999999999999999, -1, 0, 1, 4999999999999999999 }); + } + + public static void ArrayULong() + { + X(Y(), new ulong[] { 1, 2000000000, 3000000000, 4, 5, 6, 7, 8, 4999999999999999999, 9999999999999999999 }); + } + + public static void ArrayFloat() + { + X(Y(), new float[] { -1.5f, 0f, 1.5f, float.NegativeInfinity, float.PositiveInfinity, float.NaN }); + } + + public static void ArrayDouble() + { + X(Y(), new double[] { -1.5, 0.0, 1.5, double.NegativeInfinity, double.PositiveInfinity, double.NaN }); + } + + public static void ArrayDecimal() + { + X(Y(), new decimal[] { -100m, 0m, 100m, decimal.MinValue, decimal.MaxValue, 0.0000001m }); + } + + public static void ArrayString() + { + X(Y(), new string[] { "", null, "Hello", "World" }); + } + #endregion + + public static void CollectionInitializerList() + { + X(Y(), new List { 1, 2, 3 }); + } + + public static void CollectionInitializerDictionary() + { + X(Y(), new Dictionary { + { "First", 1 }, + { "Second", 2 }, + { "Third" , 3 } + }); + } +} diff --git a/ICSharpCode.Decompiler/Tests/Switch.cs b/ICSharpCode.Decompiler/Tests/Switch.cs new file mode 100644 index 000000000..3f700bbc1 --- /dev/null +++ b/ICSharpCode.Decompiler/Tests/Switch.cs @@ -0,0 +1,60 @@ +// 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; + +public static class Switch +{ + public static string ShortSwitchOverString(string text) + { + switch (text) { + case "First case": + return "Text"; + default: + return "Default"; + } + } + + public static string SwitchOverString1(string text) + { + switch (text) { + case "First case": + return "Text1"; + case "Second case": + case "2nd case": + return "Text2"; + case "Third case": + return "Text3"; + case "Fourth case": + return "Text4"; + case "Fifth case": + return "Text5"; + case "Sixth case": + return "Text6"; + case null: + return null; + default: + return "Default"; + } + } + + public static string SwitchOverString2() + { + switch (Environment.UserName) { + case "First case": + return "Text1"; + case "Second case": + return "Text2"; + case "Third case": + return "Text3"; + case "Fourth case": + return "Text4"; + case "Fifth case": + return "Text5"; + case "Sixth case": + return "Text6"; + default: + return "Default"; + } + } +} diff --git a/ICSharpCode.Decompiler/Tests/ValueTypes.cs b/ICSharpCode.Decompiler/Tests/ValueTypes.cs index e2e2d20fc..91b19dccd 100644 --- a/ICSharpCode.Decompiler/Tests/ValueTypes.cs +++ b/ICSharpCode.Decompiler/Tests/ValueTypes.cs @@ -87,4 +87,27 @@ public static class ValueTypes // test passing through by-ref arguments Copy4(ref p, out o); } + + public static void Issue56(int i, out string str) + { + str = "qq"; + str += i.ToString(); + } + + public static void CopyAroundAndModifyField(S s) + { + S locS = s; + locS.Field += 10; + s = locS; + } + + static int[] MakeArray() + { + return null; + } + + public static void IncrementArrayLocation() + { + MakeArray()[Environment.TickCount]++; + } } 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) { diff --git a/ILSpy/BamlDecompiler.cs b/ILSpy/BamlDecompiler.cs index 66a38bbae..1394e878a 100644 --- a/ILSpy/BamlDecompiler.cs +++ b/ILSpy/BamlDecompiler.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; using System.Windows.Baml2006; using System.Xaml; using System.Xml; - +using System.Xml.Linq; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; @@ -32,7 +32,8 @@ namespace ICSharpCode.ILSpy.Baml Assembly assembly = Assembly.LoadFile(containingAssemblyFile); Baml2006Reader reader = new Baml2006Reader(bamlCode, new XamlReaderSettings() { ValuesMustBeString = true, LocalAssembly = assembly }); - XamlXmlWriter writer = new XamlXmlWriter(new XmlTextWriter(w) { Formatting = Formatting.Indented }, reader.SchemaContext); + XDocument doc = new XDocument(); + XamlXmlWriter writer = new XamlXmlWriter(doc.CreateWriter(), reader.SchemaContext); while (reader.Read()) { switch (reader.NodeType) { case XamlNodeType.None: @@ -64,7 +65,43 @@ namespace ICSharpCode.ILSpy.Baml throw new Exception("Invalid value for XamlNodeType"); } } - return w.ToString(); + writer.Close(); + + // Fix namespace references + string suffixToRemove = ";assembly=" + assembly.GetName().Name; + foreach (XAttribute attrib in doc.Root.Attributes()) { + if (attrib.Name.Namespace == XNamespace.Xmlns) { + if (attrib.Value.EndsWith(suffixToRemove, StringComparison.Ordinal)) { + string newNamespace = attrib.Value.Substring(0, attrib.Value.Length - suffixToRemove.Length); + ChangeXmlNamespace(doc, attrib.Value, newNamespace); + attrib.Value = newNamespace; + } + } + } + // Convert x:Key into an attribute where possible + XName xKey = XName.Get("Key", "http://schemas.microsoft.com/winfx/2006/xaml"); + foreach (XElement e in doc.Descendants(xKey).ToList()) { + if (e.Nodes().Count() != 1) + continue; + XText text = e.Nodes().Single() as XText; + if (text != null) { + e.Parent.SetAttributeValue(xKey, text.Value); + e.Remove(); + } + } + + return doc.ToString(); + } + + /// + /// Changes all references from oldNamespace to newNamespace in the document. + /// + void ChangeXmlNamespace(XDocument doc, XNamespace oldNamespace, XNamespace newNamespace) + { + foreach (XElement e in doc.Descendants()) { + if (e.Name.Namespace == oldNamespace) + e.Name = newNamespace + e.Name.LocalName; + } } } diff --git a/ILSpy/CSharpLanguage.cs b/ILSpy/CSharpLanguage.cs index 5c24eadb6..1234c90be 100644 --- a/ILSpy/CSharpLanguage.cs +++ b/ILSpy/CSharpLanguage.cs @@ -125,6 +125,8 @@ namespace ICSharpCode.ILSpy WriteProjectFile(new TextOutputWriter(output), files, assembly.MainModule); } else { foreach (TypeDefinition type in assembly.MainModule.Types) { + if (AstBuilder.MemberIsHidden(type, options.DecompilerSettings)) + continue; AstBuilder codeDomBuilder = CreateAstBuilder(options, type); codeDomBuilder.AddType(type); codeDomBuilder.GenerateCode(output, transformAbortCondition); @@ -266,9 +268,18 @@ namespace ICSharpCode.ILSpy #endregion #region WriteCodeFilesInProject + bool IncludeTypeWhenDecompilingProject(TypeDefinition type, DecompilationOptions options) + { + if (type.Name == "" || AstBuilder.MemberIsHidden(type, options.DecompilerSettings)) + return false; + if (type.Namespace == "XamlGeneratedNamespace" && type.Name == "GeneratedInternalTypeHelper") + return false; + return true; + } + IEnumerable> WriteCodeFilesInProject(AssemblyDefinition assembly, DecompilationOptions options, HashSet directories) { - var files = assembly.MainModule.Types.Where(t => t.Name != "").GroupBy( + var files = assembly.MainModule.Types.Where(t => IncludeTypeWhenDecompilingProject(t, options)).GroupBy( delegate (TypeDefinition type) { string file = TextView.DecompilerTextView.CleanUpName(type.Name) + this.FileExtension; if (string.IsNullOrEmpty(type.Namespace)) { diff --git a/ILSpy/ILAstLanguage.cs b/ILSpy/ILAstLanguage.cs index fc6217a48..86e86a9e5 100644 --- a/ILSpy/ILAstLanguage.cs +++ b/ILSpy/ILAstLanguage.cs @@ -80,7 +80,7 @@ namespace ICSharpCode.ILSpy internal static IEnumerable GetDebugLanguages() { yield return new ILAstLanguage { name = "ILAst (unoptimized)", inlineVariables = false }; - string nextName = "ILAst (variable inlining)"; + string nextName = "ILAst (variable splitting)"; foreach (ILAstOptimizationStep step in Enum.GetValues(typeof(ILAstOptimizationStep))) { yield return new ILAstLanguage { name = nextName, abortBeforeStep = step }; nextName = "ILAst (after " + step + ")"; diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/PatternMatching/IPatternAstVisitor.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/PatternMatching/IPatternAstVisitor.cs index 7b29a00c4..6a08be6e3 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/PatternMatching/IPatternAstVisitor.cs +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/PatternMatching/IPatternAstVisitor.cs @@ -17,6 +17,7 @@ namespace ICSharpCode.NRefactory.CSharp.PatternMatching S VisitChoice(Choice choice, T data); S VisitNamedNode(NamedNode namedNode, T data); S VisitRepeat(Repeat repeat, T data); + S VisitOptionalNode(OptionalNode optionalNode, T data); S VisitIdentifierExpressionBackreference(IdentifierExpressionBackreference identifierExpressionBackreference, T data); } } diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/PatternMatching/OptionalNode.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/PatternMatching/OptionalNode.cs new file mode 100644 index 000000000..349f4393d --- /dev/null +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/PatternMatching/OptionalNode.cs @@ -0,0 +1,42 @@ +// 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; + +namespace ICSharpCode.NRefactory.CSharp.PatternMatching +{ + public class OptionalNode : Pattern + { + public static readonly Role ElementRole = new Role("Element", AstNode.Null); + + public OptionalNode(AstNode childNode) + { + AddChild(childNode, ElementRole); + } + + public OptionalNode(string groupName, AstNode childNode) : this(new NamedNode(groupName, childNode)) + { + } + + internal override bool DoMatchCollection(Role role, AstNode pos, Match match, Stack backtrackingStack) + { + backtrackingStack.Push(new PossibleMatch(pos, match.CheckPoint())); + return GetChildByRole(ElementRole).DoMatch(pos, match); + } + + protected internal override bool DoMatch(AstNode other, Match match) + { + if (other == null || other.IsNull) + return true; + else + return GetChildByRole(ElementRole).DoMatch(other, match); + } + + public override S AcceptVisitor(IAstVisitor visitor, T data) + { + return ((IPatternAstVisitor)visitor).VisitOptionalNode(this, data); + } + } +} diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/PatternMatching/Pattern.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/PatternMatching/Pattern.cs index ed2122d19..aa8d95fb7 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/PatternMatching/Pattern.cs +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/PatternMatching/Pattern.cs @@ -74,6 +74,11 @@ namespace ICSharpCode.NRefactory.CSharp.PatternMatching return p != null ? new AttributeSectionPlaceholder(p) : null; } + public static implicit operator SwitchSection(Pattern p) + { + return p != null ? new SwitchSectionPlaceholder(p) : null; + } + // Make debugging easier by giving Patterns a ToString() implementation public override string ToString() { diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/PatternMatching/Placeholder.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/PatternMatching/Placeholder.cs index 67987595f..2e3c2b592 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/PatternMatching/Placeholder.cs +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/PatternMatching/Placeholder.cs @@ -181,4 +181,33 @@ namespace ICSharpCode.NRefactory.CSharp.PatternMatching return child.DoMatchCollection(role, pos, match, backtrackingStack); } } + + sealed class SwitchSectionPlaceholder : SwitchSection + { + readonly AstNode child; + + public SwitchSectionPlaceholder(AstNode child) + { + this.child = child; + } + + public override NodeType NodeType { + get { return NodeType.Placeholder; } + } + + public override S AcceptVisitor(IAstVisitor visitor, T data) + { + return ((IPatternAstVisitor)visitor).VisitPlaceholder(this, child, data); + } + + protected internal override bool DoMatch(AstNode other, Match match) + { + return child.DoMatch(other, match); + } + + internal override bool DoMatchCollection(Role role, AstNode pos, Match match, Stack backtrackingStack) + { + return child.DoMatchCollection(role, pos, match, backtrackingStack); + } + } } diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs index bcab463d7..f12d3ffac 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs @@ -2170,6 +2170,16 @@ namespace ICSharpCode.NRefactory.CSharp RPar(); return EndNode(repeat); } + + object IPatternAstVisitor.VisitOptionalNode(OptionalNode optionalNode, object data) + { + StartNode(optionalNode); + WriteKeyword("optional"); + LPar(); + optionalNode.GetChildByRole(OptionalNode.ElementRole).AcceptVisitor(this, data); + RPar(); + return EndNode(optionalNode); + } #endregion } } diff --git a/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj index 7d5ed8424..c317932d9 100644 --- a/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj +++ b/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj @@ -107,6 +107,7 @@ +